mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:42:44 +00:00 
			
		
		
		
	LibJS: Add support for SpreadExpressions in array literals for bytecode
For this it adds another opcode `Append $lhs` which appends the accumulator to the Array in $lhs, optionally spreading it.
This commit is contained in:
		
							parent
							
								
									4235b2020f
								
							
						
					
					
						commit
						ae52ae8f9f
					
				
					 4 changed files with 109 additions and 27 deletions
				
			
		|  | @ -7,6 +7,7 @@ | |||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include <AK/Find.h> | ||||
| #include <AK/Format.h> | ||||
| #include <LibJS/AST.h> | ||||
| #include <LibJS/Bytecode/Generator.h> | ||||
|  | @ -1085,34 +1086,50 @@ Bytecode::CodeGenerationErrorOr<void> ObjectExpression::generate_bytecode(Byteco | |||
| 
 | ||||
| Bytecode::CodeGenerationErrorOr<void> ArrayExpression::generate_bytecode(Bytecode::Generator& generator) const | ||||
| { | ||||
|     Vector<Bytecode::Register> element_regs; | ||||
|     for (auto& element : m_elements) { | ||||
|         if (element && is<SpreadExpression>(*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<SpreadExpression>(*element)) | ||||
|                 VERIFY_NOT_REACHED(); | ||||
|         } else { | ||||
|             generator.emit<Bytecode::Op::LoadImmediate>(Value {}); | ||||
|         } | ||||
|         auto& element_reg = element_regs[i++]; | ||||
|         generator.emit<Bytecode::Op::Store>(element_reg); | ||||
|     } | ||||
|     if (element_regs.is_empty()) { | ||||
|     if (m_elements.is_empty()) { | ||||
|         generator.emit<Bytecode::Op::NewArray>(); | ||||
|     } else { | ||||
|         generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(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<SpreadExpression>(*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<Bytecode::Op::LoadImmediate>(Value {}); | ||||
|         else { | ||||
|             TRY((*it)->generate_bytecode(generator)); | ||||
|         } | ||||
|         generator.emit<Bytecode::Op::Store>(reg); | ||||
|     } | ||||
| 
 | ||||
|     if (first_spread.index() != 0) | ||||
|         generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2u, AK::Array { args_start_reg, Bytecode::Register { args_start_reg.index() + static_cast<u32>(first_spread.index() - 1) } }); | ||||
|     else | ||||
|         generator.emit<Bytecode::Op::NewArray>(); | ||||
| 
 | ||||
|     if (first_spread != m_elements.end()) { | ||||
|         auto array_reg = generator.allocate_register(); | ||||
|         generator.emit<Bytecode::Op::Store>(array_reg); | ||||
|         for (auto it = first_spread; it != m_elements.end(); ++it) { | ||||
|             if (!*it) { | ||||
|                 generator.emit<Bytecode::Op::LoadImmediate>(Value {}); | ||||
|                 generator.emit<Bytecode::Op::Append>(array_reg, false); | ||||
|             } else { | ||||
|                 TRY((*it)->generate_bytecode(generator)); | ||||
|                 generator.emit<Bytecode::Op::Append>(array_reg, *it && is<SpreadExpression>(**it)); | ||||
|             } | ||||
|         } | ||||
|         generator.emit<Bytecode::Op::Load>(array_reg); | ||||
|     } | ||||
| 
 | ||||
|     return {}; | ||||
| } | ||||
| 
 | ||||
|  | @ -2389,5 +2406,4 @@ Bytecode::CodeGenerationErrorOr<void> MetaProperty::generate_bytecode(Bytecode:: | |||
| 
 | ||||
|     VERIFY_NOT_REACHED(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| 
 | ||||
| #define ENUMERATE_BYTECODE_OPS(O)    \ | ||||
|     O(Add)                           \ | ||||
|     O(Append)                        \ | ||||
|     O(BitwiseAnd)                    \ | ||||
|     O(BitwiseNot)                    \ | ||||
|     O(BitwiseOr)                     \ | ||||
|  |  | |||
|  | @ -182,6 +182,46 @@ ThrowCompletionOr<void> NewArray::execute_impl(Bytecode::Interpreter& interprete | |||
|     return {}; | ||||
| } | ||||
| 
 | ||||
| ThrowCompletionOr<void> 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<Completion> { | ||||
|             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"; | ||||
|  |  | |||
|  | @ -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<void> 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() | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Hendiadyoin1
						Hendiadyoin1