From 60bd5012fe42826c33ce6ad3d3da191e7b190cb0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Thu, 1 May 2025 16:05:24 +0300 Subject: [PATCH] LibJS: Optimize array destructuring assignment for builtin iterators ...by avoiding `{ value, done }` iterator result value allocation. This change applies the same otimization 81b6a11 added for `for..in` and `for..of`. Makes following micro benchmark go 22% faster on my computer: ```js function f() { const arr = []; for (let i = 0; i < 10_000_000; i++) { arr.push([i]); } let sum = 0; for (let [i] of arr) { sum += i; } } f(); ``` --- Libraries/LibJS/Bytecode/ASTCodegen.cpp | 10 +++------- Libraries/LibJS/Bytecode/Instruction.h | 2 +- Libraries/LibJS/Bytecode/Interpreter.cpp | 8 ++++---- Libraries/LibJS/Bytecode/Op.h | 6 +++--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 0b410d75f53..196c7b2d826 100644 --- a/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1504,8 +1504,8 @@ static Bytecode::CodeGenerationErrorOr generate_array_binding_pattern_byte generator.switch_to_basic_block(iterator_is_not_exhausted_block); } - generator.emit(temp_iterator_result, iterator); - generator.emit_iterator_complete(is_iterator_exhausted, temp_iterator_result); + auto value = generator.allocate_register(); + generator.emit(value, is_iterator_exhausted, iterator); // We still have to check for exhaustion here. If the iterator is exhausted, // we need to bail before trying to get the value @@ -1517,10 +1517,6 @@ static Bytecode::CodeGenerationErrorOr generate_array_binding_pattern_byte generator.switch_to_basic_block(no_bail_block); - // Get the next value in the iterator - auto value = generator.allocate_register(); - generator.emit_iterator_value(value, temp_iterator_result); - auto& create_binding_block = generator.make_block(); generator.emit(Bytecode::Label { create_binding_block }); @@ -3187,7 +3183,7 @@ static Bytecode::CodeGenerationErrorOr> for_in_of_body_e auto done = generator.allocate_register(); if (iterator_kind == IteratorHint::Sync) { - generator.emit(next_value, done, *head_result.iterator); + generator.emit(next_value, done, *head_result.iterator); auto& loop_continue = generator.make_block(); generator.emit_jump_if( diff --git a/Libraries/LibJS/Bytecode/Instruction.h b/Libraries/LibJS/Bytecode/Instruction.h index 41234ef18a3..ff503ad0038 100644 --- a/Libraries/LibJS/Bytecode/Instruction.h +++ b/Libraries/LibJS/Bytecode/Instruction.h @@ -51,7 +51,6 @@ O(EnterObjectEnvironment) \ O(EnterUnwindContext) \ O(Exp) \ - O(ForOfNext) \ O(GetById) \ O(GetByIdWithThis) \ O(GetByValue) \ @@ -81,6 +80,7 @@ O(InstanceOf) \ O(IteratorClose) \ O(IteratorNext) \ + O(IteratorNextUnpack) \ O(IteratorToArray) \ O(Jump) \ O(JumpFalse) \ diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 3b3cdb24ccf..47dca23d03f 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -612,7 +612,6 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Dump); HANDLE_INSTRUCTION(EnterObjectEnvironment); HANDLE_INSTRUCTION(Exp); - HANDLE_INSTRUCTION(ForOfNext); HANDLE_INSTRUCTION(GetById); HANDLE_INSTRUCTION(GetByIdWithThis); HANDLE_INSTRUCTION(GetByValue); @@ -642,6 +641,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION(InstanceOf); HANDLE_INSTRUCTION(IteratorClose); HANDLE_INSTRUCTION(IteratorNext); + HANDLE_INSTRUCTION(IteratorNextUnpack); HANDLE_INSTRUCTION(IteratorToArray); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(LeaveFinally); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(LeaveLexicalEnvironment); @@ -2982,7 +2982,7 @@ ThrowCompletionOr IteratorNext::execute_impl(Bytecode::Interpreter& interp return {}; } -ThrowCompletionOr ForOfNext::execute_impl(Bytecode::Interpreter& interpreter) const +ThrowCompletionOr IteratorNextUnpack::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); auto& iterator_record = static_cast(interpreter.get(m_iterator_record).as_cell()); @@ -3777,9 +3777,9 @@ ByteString IteratorNext::to_byte_string_impl(Executable const& executable) const format_operand("iterator_record"sv, m_iterator_record, executable)); } -ByteString ForOfNext::to_byte_string_impl(Executable const& executable) const +ByteString IteratorNextUnpack::to_byte_string_impl(Executable const& executable) const { - return ByteString::formatted("ForOfNext {}, {}, {}", + return ByteString::formatted("IteratorNextUnpack {}, {}, {}", format_operand("dst_value"sv, m_dst_value, executable), format_operand("dst_done"sv, m_dst_done, executable), format_operand("iterator_record"sv, m_iterator_record, executable)); diff --git a/Libraries/LibJS/Bytecode/Op.h b/Libraries/LibJS/Bytecode/Op.h index 908f3a98448..b464502db50 100644 --- a/Libraries/LibJS/Bytecode/Op.h +++ b/Libraries/LibJS/Bytecode/Op.h @@ -2721,10 +2721,10 @@ private: Operand m_iterator_record; }; -class ForOfNext final : public Instruction { +class IteratorNextUnpack final : public Instruction { public: - ForOfNext(Operand dst_value, Operand dst_done, Operand iterator_record) - : Instruction(Type::ForOfNext) + IteratorNextUnpack(Operand dst_value, Operand dst_done, Operand iterator_record) + : Instruction(Type::IteratorNextUnpack) , m_dst_value(dst_value) , m_dst_done(dst_done) , m_iterator_record(iterator_record)