Skip to content

Commit 6464570

Browse files
LibJS: Avoid IteratorRecord GC-allocation in GetIterator instruction
With this change, `GetIterator` no longer GC-allocates an `IteratorRecord`. Instead, it stores the iterator record fields in bytecode registers. This avoids per-iteration allocations in patterns like: `for (let [x] of array) {}`. `IteratorRecord` now inherits from `IteratorRecordImpl`, which holds the iteration state. This allows the existing iteration helpers (`iterator_next()`, `iterator_step()`, etc.) operate on both the GC-allocated and the register-backed forms. Microbenchmarks: 1.1x array-destructuring-assignment-rest.js 1.226x array-destructuring-assignment.js
1 parent a4e3890 commit 6464570

18 files changed

+252
-235
lines changed

Libraries/LibJS/Bytecode/ASTCodegen.cpp

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,8 +1434,10 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
14341434
auto is_iterator_exhausted = generator.allocate_register();
14351435
generator.emit_mov(is_iterator_exhausted, generator.add_constant(Value(false)));
14361436

1437-
auto iterator = generator.allocate_register();
1438-
generator.emit<Bytecode::Op::GetIterator>(iterator, input_array);
1437+
auto iterator_object = generator.allocate_register();
1438+
auto iterator_next_method = generator.allocate_register();
1439+
auto iterator_done_property = generator.allocate_register();
1440+
generator.emit<Bytecode::Op::GetIterator>(iterator_object, iterator_next_method, iterator_done_property, input_array);
14391441
bool first = true;
14401442

14411443
auto assign_value_to_alias = [&](auto& alias, ScopedOperand value) {
@@ -1469,7 +1471,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
14691471

14701472
if (first) {
14711473
// The iterator has not been called, and is thus known to be not exhausted
1472-
generator.emit<Bytecode::Op::IteratorToArray>(value, iterator);
1474+
generator.emit<Bytecode::Op::IteratorToArray>(value, iterator_object, iterator_next_method, iterator_done_property);
14731475
} else {
14741476
auto& if_exhausted_block = generator.make_block();
14751477
auto& if_not_exhausted_block = generator.make_block();
@@ -1487,7 +1489,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
14871489
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { continuation_block });
14881490

14891491
generator.switch_to_basic_block(if_not_exhausted_block);
1490-
generator.emit<Bytecode::Op::IteratorToArray>(value, iterator);
1492+
generator.emit<Bytecode::Op::IteratorToArray>(value, iterator_object, iterator_next_method, iterator_done_property);
14911493
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { continuation_block });
14921494

14931495
generator.switch_to_basic_block(continuation_block);
@@ -1510,7 +1512,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
15101512
}
15111513

15121514
auto value = generator.allocate_register();
1513-
generator.emit<Bytecode::Op::IteratorNextUnpack>(value, is_iterator_exhausted, iterator);
1515+
generator.emit<Bytecode::Op::IteratorNextUnpack>(value, is_iterator_exhausted, iterator_object, iterator_next_method, iterator_done_property);
15141516

15151517
// We still have to check for exhaustion here. If the iterator is exhausted,
15161518
// we need to bail before trying to get the value
@@ -1571,7 +1573,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_array_binding_pattern_byte
15711573
Bytecode::Label { not_done_block });
15721574

15731575
generator.switch_to_basic_block(not_done_block);
1574-
generator.emit<Bytecode::Op::IteratorClose>(iterator, Completion::Type::Normal, Optional<Value> {});
1576+
generator.emit<Bytecode::Op::IteratorClose>(iterator_object, iterator_next_method, iterator_done_property, Completion::Type::Normal, Optional<Value> {});
15751577
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { done_block });
15761578

15771579
generator.switch_to_basic_block(done_block);
@@ -1980,17 +1982,12 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
19801982
auto value = TRY(m_argument->generate_bytecode(generator)).value();
19811983

19821984
// 4. Let iteratorRecord be ? GetIterator(value, generatorKind).
1983-
auto iterator_record = generator.allocate_register();
1984-
auto iterator_hint = generator.is_in_async_generator_function() ? IteratorHint::Async : IteratorHint::Sync;
1985-
generator.emit<Bytecode::Op::GetIterator>(iterator_record, value, iterator_hint);
1986-
19871985
// 5. Let iterator be iteratorRecord.[[Iterator]].
19881986
auto iterator = generator.allocate_register();
1989-
generator.emit<Bytecode::Op::GetObjectFromIteratorRecord>(iterator, iterator_record);
1990-
1991-
// Cache iteratorRecord.[[NextMethod]] for use in step 7.a.i.
19921987
auto next_method = generator.allocate_register();
1993-
generator.emit<Bytecode::Op::GetNextMethodFromIteratorRecord>(next_method, iterator_record);
1988+
auto iterator_done_property = generator.allocate_register();
1989+
auto iterator_hint = generator.is_in_async_generator_function() ? IteratorHint::Async : IteratorHint::Sync;
1990+
generator.emit<Bytecode::Op::GetIterator>(iterator, next_method, iterator_done_property, value, iterator_hint);
19941991

19951992
// 6. Let received be NormalCompletion(undefined).
19961993
// See get_received_completion_type_and_value above.
@@ -2165,11 +2162,11 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
21652162
// 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
21662163
if (generator.is_in_async_generator_function()) {
21672164
// FIXME: This performs `await` outside of the generator!
2168-
generator.emit<Bytecode::Op::AsyncIteratorClose>(iterator_record, Completion::Type::Normal, Optional<Value> {});
2165+
generator.emit<Bytecode::Op::AsyncIteratorClose>(iterator, next_method, done, Completion::Type::Normal, Optional<Value> {});
21692166
}
21702167
// 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
21712168
else {
2172-
generator.emit<Bytecode::Op::IteratorClose>(iterator_record, Completion::Type::Normal, Optional<Value> {});
2169+
generator.emit<Bytecode::Op::IteratorClose>(iterator, next_method, done, Completion::Type::Normal, Optional<Value> {});
21732170
}
21742171

21752172
// 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does not have a throw method.
@@ -3049,7 +3046,9 @@ enum class IterationKind {
30493046
struct ForInOfHeadEvaluationResult {
30503047
bool is_destructuring { false };
30513048
LHSKind lhs_kind { LHSKind::Assignment };
3052-
Optional<ScopedOperand> iterator;
3049+
Optional<ScopedOperand> iterator_object;
3050+
Optional<ScopedOperand> iterator_next_method;
3051+
Optional<ScopedOperand> iterator_done_property;
30533052
};
30543053
static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_head_evaluation(Bytecode::Generator& generator, IterationKind iteration_kind, Variant<NonnullRefPtr<ASTNode const>, NonnullRefPtr<BindingPattern const>> const& lhs, NonnullRefPtr<ASTNode const> const& rhs)
30553054
{
@@ -3122,7 +3121,9 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
31223121
// 5. Let exprValue be ? GetValue(exprRef).
31233122
// NOTE: No need to store this anywhere.
31243123

3125-
auto iterator = generator.allocate_register();
3124+
auto iterator_object = generator.allocate_register();
3125+
auto iterator_next_method = generator.allocate_register();
3126+
auto iterator_done_property = generator.allocate_register();
31263127

31273128
// 6. If iterationKind is enumerate, then
31283129
if (iteration_kind == IterationKind::Enumerate) {
@@ -3144,7 +3145,7 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
31443145
// c. Let iterator be EnumerateObjectProperties(obj).
31453146
// d. Let nextMethod be ! GetV(iterator, "next").
31463147
// e. Return the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
3147-
generator.emit<Bytecode::Op::GetObjectPropertyIterator>(iterator, object);
3148+
generator.emit<Bytecode::Op::GetObjectPropertyIterator>(iterator_object, iterator_next_method, iterator_done_property, object);
31483149
}
31493150
// 7. Else,
31503151
else {
@@ -3154,10 +3155,12 @@ static Bytecode::CodeGenerationErrorOr<ForInOfHeadEvaluationResult> for_in_of_he
31543155
auto iterator_kind = iteration_kind == IterationKind::AsyncIterate ? IteratorHint::Async : IteratorHint::Sync;
31553156

31563157
// d. Return ? GetIterator(exprValue, iteratorKind).
3157-
generator.emit<Bytecode::Op::GetIterator>(iterator, object, iterator_kind);
3158+
generator.emit<Bytecode::Op::GetIterator>(iterator_object, iterator_next_method, iterator_done_property, object, iterator_kind);
31583159
}
31593160

3160-
result.iterator = iterator;
3161+
result.iterator_object = iterator_object;
3162+
result.iterator_next_method = iterator_next_method;
3163+
result.iterator_done_property = iterator_done_property;
31613164
return result;
31623165
}
31633166

@@ -3199,7 +3202,7 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
31993202
auto done = generator.allocate_register();
32003203

32013204
if (iterator_kind == IteratorHint::Sync) {
3202-
generator.emit<Bytecode::Op::IteratorNextUnpack>(next_value, done, *head_result.iterator);
3205+
generator.emit<Bytecode::Op::IteratorNextUnpack>(next_value, done, *head_result.iterator_object, *head_result.iterator_next_method, *head_result.iterator_done_property);
32033206

32043207
auto& loop_continue = generator.make_block();
32053208
generator.emit_jump_if(
@@ -3209,7 +3212,7 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> for_in_of_body_e
32093212
generator.switch_to_basic_block(loop_continue);
32103213
} else {
32113214
auto next_result = generator.allocate_register();
3212-
generator.emit<Bytecode::Op::IteratorNext>(next_result, *head_result.iterator);
3215+
generator.emit<Bytecode::Op::IteratorNext>(next_result, *head_result.iterator_object, *head_result.iterator_next_method, *head_result.iterator_done_property);
32133216

32143217
// b. If iteratorKind is async, set nextResult to ? Await(nextResult).
32153218
auto received_completion = generator.allocate_register();

Libraries/LibJS/Bytecode/Instruction.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@
6868
O(GetLengthWithThis) \
6969
O(GetMethod) \
7070
O(GetNewTarget) \
71-
O(GetNextMethodFromIteratorRecord) \
72-
O(GetObjectFromIteratorRecord) \
7371
O(GetObjectPropertyIterator) \
7472
O(GetPrivateById) \
7573
O(GetBinding) \

0 commit comments

Comments
 (0)