diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 19a2435dc2..c6a9512a3b 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -7,6 +7,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -1085,34 +1086,50 @@ Bytecode::CodeGenerationErrorOr ObjectExpression::generate_bytecode(Byteco Bytecode::CodeGenerationErrorOr ArrayExpression::generate_bytecode(Bytecode::Generator& generator) const { - Vector element_regs; - for (auto& element : m_elements) { - if (element && is(*element)) { - return Bytecode::CodeGenerationError { - this, - "Unimplemented element kind: SpreadExpression"sv, - }; - } - element_regs.append(generator.allocate_register()); - } - size_t i = 0; - for (auto& element : m_elements) { - if (element) { - TRY(element->generate_bytecode(generator)); - - if (is(*element)) - VERIFY_NOT_REACHED(); - } else { - generator.emit(Value {}); - } - auto& element_reg = element_regs[i++]; - generator.emit(element_reg); - } - if (element_regs.is_empty()) { + if (m_elements.is_empty()) { generator.emit(); - } else { - generator.emit_with_extra_register_slots(2u, AK::Array { element_regs.first(), element_regs.last() }); + return {}; } + + auto first_spread = find_if(m_elements.begin(), m_elements.end(), [](auto el) { return el && is(*el); }); + + Bytecode::Register args_start_reg { 0 }; + for (auto it = m_elements.begin(); it != first_spread; ++it) { + auto reg = generator.allocate_register(); + if (args_start_reg.index() == 0) + args_start_reg = reg; + } + u32 i = 0; + for (auto it = m_elements.begin(); it != first_spread; ++it, ++i) { + Bytecode::Register reg { args_start_reg.index() + i }; + if (!*it) + generator.emit(Value {}); + else { + TRY((*it)->generate_bytecode(generator)); + } + generator.emit(reg); + } + + if (first_spread.index() != 0) + generator.emit_with_extra_register_slots(2u, AK::Array { args_start_reg, Bytecode::Register { args_start_reg.index() + static_cast(first_spread.index() - 1) } }); + else + generator.emit(); + + if (first_spread != m_elements.end()) { + auto array_reg = generator.allocate_register(); + generator.emit(array_reg); + for (auto it = first_spread; it != m_elements.end(); ++it) { + if (!*it) { + generator.emit(Value {}); + generator.emit(array_reg, false); + } else { + TRY((*it)->generate_bytecode(generator)); + generator.emit(array_reg, *it && is(**it)); + } + } + generator.emit(array_reg); + } + return {}; } @@ -2389,5 +2406,4 @@ Bytecode::CodeGenerationErrorOr MetaProperty::generate_bytecode(Bytecode:: VERIFY_NOT_REACHED(); } - } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index befd238fc1..18fd91de85 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -12,6 +12,7 @@ #define ENUMERATE_BYTECODE_OPS(O) \ O(Add) \ + O(Append) \ O(BitwiseAnd) \ O(BitwiseNot) \ O(BitwiseOr) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 54fa8d31ab..18d43820dc 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -182,6 +182,46 @@ ThrowCompletionOr NewArray::execute_impl(Bytecode::Interpreter& interprete return {}; } +ThrowCompletionOr Append::execute_impl(Bytecode::Interpreter& interpreter) const +{ + // Note: This OpCode is used to construct array literals containing at least one spread element, + // Iterating over such a spread element to unpack it has to be visible by + // the user courtesy of + // https://tc39.es/ecma262/#sec-runtime-semantics-arrayaccumulation + // SpreadElement : ... AssignmentExpression + // 1. Let spreadRef be ? Evaluation of AssignmentExpression. + // 2. Let spreadObj be ? GetValue(spreadRef). + // 3. Let iteratorRecord be ? GetIterator(spreadObj). + // 4. Repeat, + // a. Let next be ? IteratorStep(iteratorRecord). + // b. If next is false, return nextIndex. + // c. Let nextValue be ? IteratorValue(next). + // d. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(nextIndex)), nextValue). + // e. Set nextIndex to nextIndex + 1. + + auto& vm = interpreter.vm(); + + // Note: We know from codegen, that lhs is a plain array with only indexed properties + auto& lhs = interpreter.reg(m_lhs).as_array(); + auto lhs_size = lhs.indexed_properties().array_like_size(); + + auto rhs = interpreter.accumulator(); + + if (m_is_spread) { + // ...rhs + size_t i = lhs_size; + TRY(get_iterator_values(vm, rhs, [&i, &lhs](Value iterator_value) -> Optional { + lhs.indexed_properties().put(i, iterator_value, default_attributes); + ++i; + return {}; + })); + } else { + lhs.indexed_properties().put(lhs_size, rhs, default_attributes); + } + + return {}; +} + // FIXME: Since the accumulator is a Value, we store an object there and have to convert back and forth between that an Iterator records. Not great. // Make sure to put this into the accumulator before the iterator object disappears from the stack to prevent the members from being GC'd. static Object* iterator_to_object(VM& vm, Iterator iterator) @@ -943,6 +983,13 @@ String NewArray::to_string_impl(Bytecode::Executable const&) const return builder.to_string(); } +String Append::to_string_impl(Bytecode::Executable const&) const +{ + if (m_is_spread) + return String::formatted("Append lhs: **{}", m_lhs); + return String::formatted("Append lhs: {}", m_lhs); +} + String IteratorToArray::to_string_impl(Bytecode::Executable const&) const { return "IteratorToArray"; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index cdecccd4bf..a8c4fbf855 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -254,6 +254,24 @@ private: Register m_elements[]; }; +class Append final : public Instruction { +public: + Append(Register lhs, bool is_spread) + : Instruction(Type::Append) + , m_lhs(lhs) + , m_is_spread(is_spread) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + +private: + Register m_lhs; + bool m_is_spread = false; +}; + class IteratorToArray final : public Instruction { public: IteratorToArray()