Browse Source

LibJS: Support object rest elements in the bytecode interpreter

Matthew Olsson 4 năm trước cách đây
mục cha
commit
25baefdd1e

+ 33 - 2
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -643,20 +643,51 @@ static void generate_binding_pattern_bytecode(Bytecode::Generator& generator, Bi
 
 static void generate_object_binding_pattern_bytecode(Bytecode::Generator& generator, BindingPattern const& pattern, Bytecode::Register const& value_reg)
 {
+    Vector<Bytecode::Register> excluded_property_names;
+    auto has_rest = false;
+    if (pattern.entries.size() > 0)
+        has_rest = pattern.entries[pattern.entries.size() - 1].is_rest;
+
     for (auto& [name, alias, initializer, is_rest] : pattern.entries) {
-        if (is_rest)
-            TODO();
+        if (is_rest) {
+            VERIFY(name.has<NonnullRefPtr<Identifier>>());
+            VERIFY(alias.has<Empty>());
+            VERIFY(!initializer);
+
+            auto identifier = name.get<NonnullRefPtr<Identifier>>()->string();
+            auto interned_identifier = generator.intern_string(identifier);
+
+            generator.emit_with_extra_register_slots<Bytecode::Op::CopyObjectExcludingProperties>(excluded_property_names.size(), value_reg, excluded_property_names);
+            generator.emit<Bytecode::Op::SetVariable>(interned_identifier);
+
+            return;
+        }
 
         Bytecode::StringTableIndex name_index;
 
         if (name.has<NonnullRefPtr<Identifier>>()) {
             auto identifier = name.get<NonnullRefPtr<Identifier>>()->string();
             name_index = generator.intern_string(identifier);
+
+            if (has_rest) {
+                auto excluded_name_reg = generator.allocate_register();
+                excluded_property_names.append(excluded_name_reg);
+                generator.emit<Bytecode::Op::NewString>(name_index);
+                generator.emit<Bytecode::Op::Store>(excluded_name_reg);
+            }
+
             generator.emit<Bytecode::Op::Load>(value_reg);
             generator.emit<Bytecode::Op::GetById>(name_index);
         } else {
             auto expression = name.get<NonnullRefPtr<Expression>>();
             expression->generate_bytecode(generator);
+
+            if (has_rest) {
+                auto excluded_name_reg = generator.allocate_register();
+                excluded_property_names.append(excluded_name_reg);
+                generator.emit<Bytecode::Op::Store>(excluded_name_reg);
+            }
+
             generator.emit<Bytecode::Op::GetByValue>(value_reg);
         }
 

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

@@ -32,6 +32,7 @@
     O(IteratorToArray)               \
     O(NewString)                     \
     O(NewObject)                     \
+    O(CopyObjectExcludingProperties) \
     O(GetVariable)                   \
     O(SetVariable)                   \
     O(PutById)                       \
@@ -103,5 +104,4 @@ protected:
 private:
     Type m_type {};
 };
-
 }

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

@@ -6,6 +6,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <AK/HashTable.h>
 #include <LibJS/AST.h>
 #include <LibJS/Bytecode/Interpreter.h>
 #include <LibJS/Bytecode/Op.h>
@@ -168,6 +169,36 @@ void NewObject::execute_impl(Bytecode::Interpreter& interpreter) const
     interpreter.accumulator() = Object::create(interpreter.global_object(), interpreter.global_object().object_prototype());
 }
 
+void CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    auto* from_object = interpreter.reg(m_from_object).to_object(interpreter.global_object());
+    if (interpreter.vm().exception())
+        return;
+
+    auto* to_object = Object::create(interpreter.global_object(), interpreter.global_object().object_prototype());
+
+    HashTable<Value, ValueTraits> excluded_names;
+    for (size_t i = 0; i < m_excluded_names_count; ++i) {
+        excluded_names.set(interpreter.reg(m_excluded_names[i]));
+        if (interpreter.vm().exception())
+            return;
+    }
+
+    auto own_keys = from_object->get_own_properties(Object::PropertyKind::Key, true);
+
+    for (auto& key : own_keys) {
+        if (!excluded_names.contains(key)) {
+            auto property_name = PropertyName(key.to_property_key(interpreter.global_object()));
+            auto property_value = from_object->get(property_name);
+            if (interpreter.vm().exception())
+                return;
+            to_object->define_property(property_name, property_value);
+        }
+    }
+
+    interpreter.accumulator() = to_object;
+}
+
 void ConcatString::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     interpreter.reg(m_lhs) = add(interpreter.global_object(), interpreter.reg(m_lhs), interpreter.accumulator());
@@ -467,6 +498,22 @@ String NewObject::to_string_impl(Bytecode::Executable const&) const
     return "NewObject";
 }
 
+String CopyObjectExcludingProperties::to_string_impl(const Bytecode::Executable&) const
+{
+    StringBuilder builder;
+    builder.appendff("CopyObjectExcludingProperties from:{}", m_from_object);
+    if (m_excluded_names_count != 0) {
+        builder.append(" excluding:[");
+        for (size_t i = 0; i < m_excluded_names_count; ++i) {
+            builder.appendff("{}", m_excluded_names[i]);
+            if (i != m_excluded_names_count - 1)
+                builder.append(',');
+        }
+        builder.append(']');
+    }
+    return builder.to_string();
+}
+
 String ConcatString::to_string_impl(Bytecode::Executable const&) const
 {
     return String::formatted("ConcatString {}", m_lhs);

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

@@ -162,6 +162,30 @@ public:
     void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
 };
 
+// NOTE: This instruction is variable-width depending on the number of excluded names
+class CopyObjectExcludingProperties final : public Instruction {
+public:
+    CopyObjectExcludingProperties(Register from_object, Vector<Register> const& excluded_names)
+        : Instruction(Type::CopyObjectExcludingProperties)
+        , m_from_object(from_object)
+        , m_excluded_names_count(excluded_names.size())
+    {
+        for (size_t i = 0; i < m_excluded_names_count; i++)
+            m_excluded_names[i] = excluded_names[i];
+    }
+
+    void execute_impl(Bytecode::Interpreter&) const;
+    String to_string_impl(Bytecode::Executable const&) const;
+    void replace_references_impl(BasicBlock const&, BasicBlock const&) { }
+
+    size_t length_impl() const { return sizeof(*this) + sizeof(Register) * m_excluded_names_count; }
+
+private:
+    Register m_from_object;
+    size_t m_excluded_names_count { 0 };
+    Register m_excluded_names[];
+};
+
 class NewBigInt final : public Instruction {
 public:
     explicit NewBigInt(Crypto::SignedBigInteger bigint)
@@ -713,6 +737,8 @@ ALWAYS_INLINE size_t Instruction::length() const
         return static_cast<Op::Call const&>(*this).length_impl();
     else if (type() == Type::NewArray)
         return static_cast<Op::NewArray const&>(*this).length_impl();
+    else if (type() == Type::CopyObjectExcludingProperties)
+        return static_cast<Op::CopyObjectExcludingProperties const&>(*this).length_impl();
 
 #define __BYTECODE_OP(op) \
     case Type::op:        \