Jelajahi Sumber

LibJS: Generate bytecode for entering nested lexical environments

This adds a new PushLexicalEnvironment instruction that creates a new
LexicalEnvironment and pushes it on the VM's scope stack.

There is no corresponding PopLexicalEnvironment instruction yet,
so this will behave incorrectly with let/const scopes for example.
Andreas Kling 4 tahun lalu
induk
melakukan
c3c68399b5

+ 51 - 1
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -11,6 +11,8 @@
 #include <LibJS/Bytecode/Instruction.h>
 #include <LibJS/Bytecode/Op.h>
 #include <LibJS/Bytecode/Register.h>
+#include <LibJS/Bytecode/StringTable.h>
+#include <LibJS/Runtime/ScopeObject.h>
 
 namespace JS {
 
@@ -27,6 +29,41 @@ void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const
         generator.emit<Bytecode::Op::SetVariable>(generator.intern_string(function.name()));
     }
 
+    HashMap<u32, Variable> scope_variables_with_declaration_kind;
+
+    bool is_program_node = is<Program>(*this);
+    for (auto& declaration : variables()) {
+        for (auto& declarator : declaration.declarations()) {
+            if (is_program_node && declaration.declaration_kind() == DeclarationKind::Var) {
+                declarator.target().visit(
+                    [&](const NonnullRefPtr<Identifier>& id) {
+                        generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
+                        generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(id->string()));
+                    },
+                    [&](const NonnullRefPtr<BindingPattern>& binding) {
+                        binding->for_each_assigned_name([&](const auto& name) {
+                            generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
+                            generator.emit<Bytecode::Op::PutById>(Bytecode::Register::global_object(), generator.intern_string(name));
+                        });
+                    });
+            } else {
+                declarator.target().visit(
+                    [&](const NonnullRefPtr<Identifier>& id) {
+                        scope_variables_with_declaration_kind.set((size_t)generator.intern_string(id->string()).value(), { js_undefined(), declaration.declaration_kind() });
+                    },
+                    [&](const NonnullRefPtr<BindingPattern>& binding) {
+                        binding->for_each_assigned_name([&](const auto& name) {
+                            scope_variables_with_declaration_kind.set((size_t)generator.intern_string(name).value(), { js_undefined(), declaration.declaration_kind() });
+                        });
+                    });
+            }
+        }
+    }
+
+    if (!scope_variables_with_declaration_kind.is_empty()) {
+        generator.emit<Bytecode::Op::PushLexicalEnvironment>(move(scope_variables_with_declaration_kind));
+    }
+
     for (auto& child : children()) {
         child.generate_bytecode(generator);
         if (generator.is_current_block_terminated())
@@ -560,8 +597,21 @@ void FunctionDeclaration::generate_bytecode(Bytecode::Generator&) const
 {
 }
 
-void VariableDeclaration::generate_bytecode(Bytecode::Generator&) const
+void VariableDeclaration::generate_bytecode(Bytecode::Generator& generator) const
 {
+    for (auto& declarator : m_declarations) {
+        if (declarator.init())
+            declarator.init()->generate_bytecode(generator);
+        else
+            generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
+        declarator.target().visit(
+            [&](const NonnullRefPtr<Identifier>& id) {
+                generator.emit<Bytecode::Op::SetVariable>(generator.intern_string(id->string()));
+            },
+            [&](const NonnullRefPtr<BindingPattern>&) {
+                TODO();
+            });
+    }
 }
 
 void CallExpression::generate_bytecode(Bytecode::Generator& generator) const

+ 1 - 0
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -58,6 +58,7 @@
     O(Increment)                  \
     O(Decrement)                  \
     O(Throw)                      \
+    O(PushLexicalEnvironment)     \
     O(EnterUnwindContext)         \
     O(LeaveUnwindContext)         \
     O(ContinuePendingUnwind)

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

@@ -12,6 +12,8 @@
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/BigInt.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/LexicalEnvironment.h>
+#include <LibJS/Runtime/ScopeObject.h>
 #include <LibJS/Runtime/ScriptFunction.h>
 #include <LibJS/Runtime/Value.h>
 
@@ -266,6 +268,15 @@ void ContinuePendingUnwind::execute(Bytecode::Interpreter& interpreter) const
     interpreter.continue_pending_unwind(m_resume_target);
 }
 
+void PushLexicalEnvironment::execute(Bytecode::Interpreter& interpreter) const
+{
+    HashMap<FlyString, Variable> resolved_variables;
+    for (auto& it : m_variables)
+        resolved_variables.set(interpreter.current_executable().get_string(it.key), it.value);
+    auto* block_lexical_environment = interpreter.vm().heap().allocate<LexicalEnvironment>(interpreter.global_object(), move(resolved_variables), interpreter.vm().current_scope());
+    interpreter.vm().call_frame().scope = block_lexical_environment;
+}
+
 String Load::to_string(Bytecode::Executable const&) const
 {
     return String::formatted("Load {}", m_src);
@@ -416,4 +427,19 @@ String ContinuePendingUnwind::to_string(Bytecode::Executable const&) const
     return String::formatted("ContinuePendingUnwind resume:{}", m_resume_target);
 }
 
+String PushLexicalEnvironment::to_string(const Bytecode::Executable& executable) const
+{
+    StringBuilder builder;
+    builder.append("PushLexicalEnvironment");
+    if (!m_variables.is_empty()) {
+        builder.append(" {");
+        Vector<String> names;
+        for (auto& it : m_variables)
+            names.append(executable.get_string(it.key));
+        builder.join(", ", names);
+        builder.append("}");
+    }
+    return builder.to_string();
+}
+
 }

+ 14 - 0
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -14,6 +14,7 @@
 #include <LibJS/Bytecode/Register.h>
 #include <LibJS/Bytecode/StringTable.h>
 #include <LibJS/Heap/Cell.h>
+#include <LibJS/Runtime/ScopeObject.h>
 #include <LibJS/Runtime/Value.h>
 
 namespace JS::Bytecode::Op {
@@ -453,6 +454,19 @@ public:
 private:
     Label m_resume_target;
 };
+
+class PushLexicalEnvironment final : public Instruction {
+public:
+    PushLexicalEnvironment(HashMap<u32, Variable> variables)
+        : Instruction(Type::PushLexicalEnvironment)
+        , m_variables(move(variables))
+    {
+    }
+    void execute(Bytecode::Interpreter&) const;
+    String to_string(Bytecode::Executable const&) const;
+
+    HashMap<u32, Variable> m_variables;
+};
 }
 
 namespace JS::Bytecode {