From 25baefdd1e7c8efefd4ddf5cb2af363b4e9ebd22 Mon Sep 17 00:00:00 2001 From: Matthew Olsson Date: Sun, 13 Jun 2021 15:30:32 -0700 Subject: [PATCH] LibJS: Support object rest elements in the bytecode interpreter --- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 35 +++++++++++++- .../Libraries/LibJS/Bytecode/Instruction.h | 2 +- Userland/Libraries/LibJS/Bytecode/Op.cpp | 47 +++++++++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 26 ++++++++++ 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index ca54c0a7df..885c5d01b9 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/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 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>()); + VERIFY(alias.has()); + VERIFY(!initializer); + + auto identifier = name.get>()->string(); + auto interned_identifier = generator.intern_string(identifier); + + generator.emit_with_extra_register_slots(excluded_property_names.size(), value_reg, excluded_property_names); + generator.emit(interned_identifier); + + return; + } Bytecode::StringTableIndex name_index; if (name.has>()) { auto identifier = name.get>()->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(name_index); + generator.emit(excluded_name_reg); + } + generator.emit(value_reg); generator.emit(name_index); } else { auto expression = name.get>(); expression->generate_bytecode(generator); + + if (has_rest) { + auto excluded_name_reg = generator.allocate_register(); + excluded_property_names.append(excluded_name_reg); + generator.emit(excluded_name_reg); + } + generator.emit(value_reg); } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 55fe69d7d3..5b8f2d06ca 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/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 {}; }; - } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index a624d02663..08c84e577b 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -6,6 +6,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -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 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); diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 9c2475ed66..026f1912f3 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/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 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(*this).length_impl(); else if (type() == Type::NewArray) return static_cast(*this).length_impl(); + else if (type() == Type::CopyObjectExcludingProperties) + return static_cast(*this).length_impl(); #define __BYTECODE_OP(op) \ case Type::op: \