mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-06 00:51:51 +00:00
LibJS: Skip iterator result object allocation in for..of and for..in
Introduce special instruction for `for..of` and `for..in` loop that skips `{ value, done }` result object allocation if iterator is builtin (array, map, set, string). This reduces GC pressure significantly and avoids extracting the `value` and `done` properties. This change makes this micro benchmark 48% faster on my computer: ```js const arr = new Array(10_000_000); let counter = 0; for (let _ of arr) { counter++; } ```
This commit is contained in:
parent
ab52d86a69
commit
81b6a1100e
Notes:
github-actions[bot]
2025-04-30 18:52:38 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 81b6a1100e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4533
4 changed files with 94 additions and 23 deletions
|
@ -3183,11 +3183,23 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
|
||||||
generator.begin_continuable_scope(Bytecode::Label { loop_update }, label_set);
|
generator.begin_continuable_scope(Bytecode::Label { loop_update }, label_set);
|
||||||
|
|
||||||
// a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
// a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||||
auto next_result = generator.allocate_register();
|
auto next_value = generator.allocate_register();
|
||||||
generator.emit<Bytecode::Op::IteratorNext>(next_result, *head_result.iterator);
|
auto done = generator.allocate_register();
|
||||||
|
|
||||||
// b. If iteratorKind is async, set nextResult to ? Await(nextResult).
|
if (iterator_kind == IteratorHint::Sync) {
|
||||||
if (iterator_kind == IteratorHint::Async) {
|
generator.emit<Bytecode::Op::ForOfNext>(next_value, done, *head_result.iterator);
|
||||||
|
|
||||||
|
auto& loop_continue = generator.make_block();
|
||||||
|
generator.emit_jump_if(
|
||||||
|
done,
|
||||||
|
Bytecode::Label { loop_end },
|
||||||
|
Bytecode::Label { loop_continue });
|
||||||
|
generator.switch_to_basic_block(loop_continue);
|
||||||
|
} else {
|
||||||
|
auto next_result = generator.allocate_register();
|
||||||
|
generator.emit<Bytecode::Op::IteratorNext>(next_result, *head_result.iterator);
|
||||||
|
|
||||||
|
// b. If iteratorKind is async, set nextResult to ? Await(nextResult).
|
||||||
auto received_completion = generator.allocate_register();
|
auto received_completion = generator.allocate_register();
|
||||||
auto received_completion_type = generator.allocate_register();
|
auto received_completion_type = generator.allocate_register();
|
||||||
auto received_completion_value = generator.allocate_register();
|
auto received_completion_value = generator.allocate_register();
|
||||||
|
@ -3195,27 +3207,25 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
|
||||||
generator.emit_mov(received_completion, generator.accumulator());
|
generator.emit_mov(received_completion, generator.accumulator());
|
||||||
auto new_result = generate_await(generator, next_result, received_completion, received_completion_type, received_completion_value);
|
auto new_result = generate_await(generator, next_result, received_completion, received_completion_type, received_completion_value);
|
||||||
generator.emit_mov(next_result, new_result);
|
generator.emit_mov(next_result, new_result);
|
||||||
|
|
||||||
|
// c. If Type(nextResult) is not Object, throw a TypeError exception.
|
||||||
|
generator.emit<Bytecode::Op::ThrowIfNotObject>(next_result);
|
||||||
|
|
||||||
|
// d. Let done be ? IteratorComplete(nextResult).
|
||||||
|
generator.emit_iterator_complete(done, next_result);
|
||||||
|
|
||||||
|
// e. If done is true, return V.
|
||||||
|
auto& loop_continue = generator.make_block();
|
||||||
|
generator.emit_jump_if(
|
||||||
|
done,
|
||||||
|
Bytecode::Label { loop_end },
|
||||||
|
Bytecode::Label { loop_continue });
|
||||||
|
generator.switch_to_basic_block(loop_continue);
|
||||||
|
|
||||||
|
// f. Let nextValue be ? IteratorValue(nextResult).
|
||||||
|
generator.emit_iterator_value(next_value, next_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// c. If Type(nextResult) is not Object, throw a TypeError exception.
|
|
||||||
generator.emit<Bytecode::Op::ThrowIfNotObject>(next_result);
|
|
||||||
|
|
||||||
// d. Let done be ? IteratorComplete(nextResult).
|
|
||||||
auto done = generator.allocate_register();
|
|
||||||
generator.emit_iterator_complete(done, next_result);
|
|
||||||
|
|
||||||
// e. If done is true, return V.
|
|
||||||
auto& loop_continue = generator.make_block();
|
|
||||||
generator.emit_jump_if(
|
|
||||||
done,
|
|
||||||
Bytecode::Label { loop_end },
|
|
||||||
Bytecode::Label { loop_continue });
|
|
||||||
generator.switch_to_basic_block(loop_continue);
|
|
||||||
|
|
||||||
// f. Let nextValue be ? IteratorValue(nextResult).
|
|
||||||
auto next_value = generator.allocate_register();
|
|
||||||
generator.emit_iterator_value(next_value, next_result);
|
|
||||||
|
|
||||||
// g. If lhsKind is either assignment or varBinding, then
|
// g. If lhsKind is either assignment or varBinding, then
|
||||||
if (head_result.lhs_kind != LHSKind::LexicalBinding) {
|
if (head_result.lhs_kind != LHSKind::LexicalBinding) {
|
||||||
// i. If destructuring is false, then
|
// i. If destructuring is false, then
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
O(EnterObjectEnvironment) \
|
O(EnterObjectEnvironment) \
|
||||||
O(EnterUnwindContext) \
|
O(EnterUnwindContext) \
|
||||||
O(Exp) \
|
O(Exp) \
|
||||||
|
O(ForOfNext) \
|
||||||
O(GetById) \
|
O(GetById) \
|
||||||
O(GetByIdWithThis) \
|
O(GetByIdWithThis) \
|
||||||
O(GetByValue) \
|
O(GetByValue) \
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
|
||||||
|
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -611,6 +612,7 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point)
|
||||||
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Dump);
|
HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Dump);
|
||||||
HANDLE_INSTRUCTION(EnterObjectEnvironment);
|
HANDLE_INSTRUCTION(EnterObjectEnvironment);
|
||||||
HANDLE_INSTRUCTION(Exp);
|
HANDLE_INSTRUCTION(Exp);
|
||||||
|
HANDLE_INSTRUCTION(ForOfNext);
|
||||||
HANDLE_INSTRUCTION(GetById);
|
HANDLE_INSTRUCTION(GetById);
|
||||||
HANDLE_INSTRUCTION(GetByIdWithThis);
|
HANDLE_INSTRUCTION(GetByIdWithThis);
|
||||||
HANDLE_INSTRUCTION(GetByValue);
|
HANDLE_INSTRUCTION(GetByValue);
|
||||||
|
@ -2980,6 +2982,27 @@ ThrowCompletionOr<void> IteratorNext::execute_impl(Bytecode::Interpreter& interp
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> ForOfNext::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
|
{
|
||||||
|
auto& vm = interpreter.vm();
|
||||||
|
auto& iterator_record = static_cast<IteratorRecord&>(interpreter.get(m_iterator_record).as_cell());
|
||||||
|
|
||||||
|
Value value;
|
||||||
|
bool done = false;
|
||||||
|
if (auto* builtin_iterator = iterator_record.iterator->as_builtin_iterator()) {
|
||||||
|
TRY(builtin_iterator->next(vm, done, value));
|
||||||
|
} else {
|
||||||
|
auto result = TRY(iterator_next(vm, iterator_record));
|
||||||
|
value = TRY(result->internal_get(vm.names.value, {}));
|
||||||
|
done = TRY(result->internal_get(vm.names.done, {})).to_boolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
interpreter.set(dst_done(), Value(done));
|
||||||
|
interpreter.set(dst_value(), value);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
ThrowCompletionOr<void> NewClass::execute_impl(Bytecode::Interpreter& interpreter) const
|
ThrowCompletionOr<void> NewClass::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||||
{
|
{
|
||||||
Value super_class;
|
Value super_class;
|
||||||
|
@ -3754,6 +3777,14 @@ ByteString IteratorNext::to_byte_string_impl(Executable const& executable) const
|
||||||
format_operand("iterator_record"sv, m_iterator_record, executable));
|
format_operand("iterator_record"sv, m_iterator_record, executable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ByteString ForOfNext::to_byte_string_impl(Executable const& executable) const
|
||||||
|
{
|
||||||
|
return ByteString::formatted("ForOfNext {}, {}, {}",
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
ByteString ResolveThisBinding::to_byte_string_impl(Bytecode::Executable const&) const
|
ByteString ResolveThisBinding::to_byte_string_impl(Bytecode::Executable const&) const
|
||||||
{
|
{
|
||||||
return "ResolveThisBinding"sv;
|
return "ResolveThisBinding"sv;
|
||||||
|
|
|
@ -2721,6 +2721,35 @@ private:
|
||||||
Operand m_iterator_record;
|
Operand m_iterator_record;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ForOfNext final : public Instruction {
|
||||||
|
public:
|
||||||
|
ForOfNext(Operand dst_value, Operand dst_done, Operand iterator_record)
|
||||||
|
: Instruction(Type::ForOfNext)
|
||||||
|
, m_dst_value(dst_value)
|
||||||
|
, m_dst_done(dst_done)
|
||||||
|
, m_iterator_record(iterator_record)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||||
|
ByteString to_byte_string_impl(Bytecode::Executable const&) const;
|
||||||
|
void visit_operands_impl(Function<void(Operand&)> visitor)
|
||||||
|
{
|
||||||
|
visitor(m_dst_value);
|
||||||
|
visitor(m_dst_done);
|
||||||
|
visitor(m_iterator_record);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand dst_value() const { return m_dst_value; }
|
||||||
|
Operand dst_done() const { return m_dst_done; }
|
||||||
|
Operand iterator_record() const { return m_iterator_record; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Operand m_dst_value;
|
||||||
|
Operand m_dst_done;
|
||||||
|
Operand m_iterator_record;
|
||||||
|
};
|
||||||
|
|
||||||
class ResolveThisBinding final : public Instruction {
|
class ResolveThisBinding final : public Instruction {
|
||||||
public:
|
public:
|
||||||
ResolveThisBinding()
|
ResolveThisBinding()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue