Browse Source

LibJS: Support basic function calls in the bytecode world :^)

This patch adds the Call bytecode instruction which is emitted for the
CallExpression AST node.

It's pretty barebones and doesn't handle 'this' values properly, etc.
But it can perform basic function calls! :^)

Note that the called function will *not* execute as bytecode, but will
simply fall back into the old codepath and use the AST interpreter.
Andreas Kling 4 years ago
parent
commit
dc63958478

+ 2 - 0
Userland/Libraries/LibJS/AST.h

@@ -272,6 +272,7 @@ public:
 
 
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
     virtual void dump(int indent) const override;
+    virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 };
 };
 
 
 class FunctionExpression final
 class FunctionExpression final
@@ -849,6 +850,7 @@ public:
 
 
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
     virtual void dump(int indent) const override;
+    virtual Optional<Bytecode::Register> generate_bytecode(Bytecode::Generator&) const override;
 
 
 private:
 private:
     struct ThisAndCallee {
     struct ThisAndCallee {

+ 21 - 0
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -156,4 +156,25 @@ Optional<Bytecode::Register> MemberExpression::generate_bytecode(Bytecode::Gener
     }
     }
 }
 }
 
 
+Optional<Bytecode::Register> FunctionDeclaration::generate_bytecode(Bytecode::Generator&) const
+{
+    return {};
+}
+
+Optional<Bytecode::Register> CallExpression::generate_bytecode(Bytecode::Generator& generator) const
+{
+    auto callee_reg = m_callee->generate_bytecode(generator);
+
+    // FIXME: Load the correct 'this' value into 'this_reg'.
+    auto this_reg = generator.allocate_register();
+    generator.emit<Bytecode::Op::Load>(this_reg, js_undefined());
+
+    Vector<Bytecode::Register> argument_registers;
+    for (auto& arg : m_arguments)
+        argument_registers.append(*arg.value->generate_bytecode(generator));
+    auto dst_reg = generator.allocate_register();
+    generator.emit<Bytecode::Op::Call>(dst_reg, *callee_reg, this_reg, argument_registers);
+    return dst_reg;
+}
+
 }
 }

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

@@ -91,6 +91,31 @@ void JumpIfTrue::execute(Bytecode::Interpreter& interpreter) const
         interpreter.jump(m_target.value());
         interpreter.jump(m_target.value());
 }
 }
 
 
+void Call::execute(Bytecode::Interpreter& interpreter) const
+{
+    auto callee = interpreter.reg(m_callee);
+    if (!callee.is_function()) {
+        TODO();
+    }
+    auto& function = callee.as_function();
+
+    auto this_value = interpreter.reg(m_this_value);
+
+    Value return_value;
+
+    if (m_arguments.is_empty()) {
+        return_value = interpreter.vm().call(function, this_value);
+    } else {
+        MarkedValueList argument_values { interpreter.vm().heap() };
+        for (auto& arg : m_arguments) {
+            argument_values.append(interpreter.reg(arg));
+        }
+        return_value = interpreter.vm().call(function, this_value, move(argument_values));
+    }
+
+    interpreter.reg(m_dst) = return_value;
+}
+
 void EnterScope::execute(Bytecode::Interpreter& interpreter) const
 void EnterScope::execute(Bytecode::Interpreter& interpreter) const
 {
 {
     auto& vm = interpreter.vm();
     auto& vm = interpreter.vm();
@@ -182,6 +207,22 @@ String JumpIfTrue::to_string() const
     return String::formatted("JumpIfTrue result:{}, target:<empty>", m_result);
     return String::formatted("JumpIfTrue result:{}, target:<empty>", m_result);
 }
 }
 
 
+String Call::to_string() const
+{
+    StringBuilder builder;
+    builder.appendff("Call dst:{}, callee:{}, this:{}", m_dst, m_callee, m_this_value);
+    if (!m_arguments.is_empty()) {
+        builder.append(", arguments:[");
+        for (size_t i = 0; i < m_arguments.size(); ++i) {
+            builder.appendff("{}", m_arguments[i]);
+            if (i != m_arguments.size() - 1)
+                builder.append(',');
+        }
+        builder.append(']');
+    }
+    return builder.to_string();
+}
+
 String EnterScope::to_string() const
 String EnterScope::to_string() const
 {
 {
     return "EnterScope";
     return "EnterScope";

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

@@ -265,6 +265,27 @@ private:
     Optional<Label> m_target;
     Optional<Label> m_target;
 };
 };
 
 
+class Call final : public Instruction {
+public:
+    Call(Register dst, Register callee, Register this_value, Vector<Register> arguments)
+        : m_dst(dst)
+        , m_callee(callee)
+        , m_this_value(this_value)
+        , m_arguments(move(arguments))
+    {
+    }
+
+    virtual ~Call() override { }
+    virtual void execute(Bytecode::Interpreter&) const override;
+    virtual String to_string() const override;
+
+private:
+    Register m_dst;
+    Register m_callee;
+    Register m_this_value;
+    Vector<Register> m_arguments;
+};
+
 class EnterScope final : public Instruction {
 class EnterScope final : public Instruction {
 public:
 public:
     explicit EnterScope(ScopeNode const& scope_node)
     explicit EnterScope(ScopeNode const& scope_node)