Skip to content

Fix ArrayIterator tracking and keep the live length bound - #17

Merged
NullVoxPopuli merged 3 commits into
NullVoxPopuli:nvp/perf/array-iteratorfrom
NullVoxPopuli-ai-agent:nvp/fix-array-iterator-tracking
Sep 22, 2026
Merged

NullVoxPopuli merged 3 commits into
NullVoxPopuli:nvp/perf/array-iteratorfrom
NullVoxPopuli-ai-agent:nvp/fix-array-iterator-tracking

Conversation

@NullVoxPopuli-ai-agent

@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent commented Sep 21, 2026

Copy link
Copy Markdown

Fixes the two CI failures on emberjs#21598, and keeps the behavior that johanrd reported as lost.

ArrayIterator needs two reads of length, for two different reasons.

1. A read in the constructor, for tracking

createIteratorRef builds the iterator in a tracking frame. The constructor is the only ArrayIterator code that runs in that frame. isEmpty() and next() run later, in the frame of the VM.

980f7659c2 removed the constructor read. The iterator reference then never consumed the tag of a tracked array. After a change, the reference returned the same iterator, and ListBlockOpcode skipped its sync. That caused the failures in Basic Test and Perf script still works.

The constructor now reads length and stores the empty state only, as main does.

2. A live read in next(), for plain arrays

Code in the block body or in a key getter runs between two next() calls. That code can change the length of a plain array, and a plain array has no tag.

{{#each}} over a plain array main stored bound (a5db40a9a3) this branch
4 items, truncated to 1 by the first item [item-0] [item-0][][][] [item-0]
1 item, the first item pushes one more [item-0][item-1] [item-0] [item-0][item-1]

Two new tests in the #each suite cover these rows.

Test helper

assertEachCompareResults now compares the rendered item count. Before, it looped over the rendered elements only, so {{each}} works with new items passed while push did not update the list.

Verification

  • Full local browser suite: 9463 tests, 0 failures.
  • tsc, ESLint, and Prettier pass.
  • I did not run the perf script locally. It passed in CI on a5db40a9a3, which had the constructor read.

🤖 Generated with Claude Code

NullVoxPopuli and others added 2 commits September 21, 2026 18:57
The iterator reference computes its value in a tracking frame.
The constructor is the only ArrayIterator code that runs in that frame.
`isEmpty()` and `next()` run later, in the frame of the VM.

Without a read in the constructor, the reference never consumes the tag of a tracked array.
The reference then returns the same iterator after a change, and the list block skips its sync.

This broke `{{each}} works when updating old items` for `trackedArray`.
It also stopped the list updates that the benchmark app waits for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The assertion looped over the rendered elements only.
A list that did not render a new item passed the test.

`{{each}} works with new items` passed for `trackedArray` while the list did not update after `push`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Code in the block body or in a `key` getter runs between two `next()` calls.
That code can change the length of a plain array, and a plain array has no tag.

On main, `next()` compares against the live length:
- a shorter array stops the iteration
- a longer array renders the new items

A stored length rendered blocks for `undefined` after a truncation, and skipped added items.

The constructor still reads `length`, because only that read runs in the tracking frame of the iterator reference.
It now stores the empty state only, as main does.

Reported by johanrd in emberjs#21598.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@NullVoxPopuli-ai-agent NullVoxPopuli-ai-agent changed the title Read the array length in the ArrayIterator constructor Fix ArrayIterator tracking and keep the live length bound Sep 21, 2026
@NullVoxPopuli
NullVoxPopuli merged commit 1fcf3bd into NullVoxPopuli:nvp/perf/array-iterator Sep 22, 2026
48 of 49 checks passed
@NullVoxPopuli
NullVoxPopuli deleted the nvp/fix-array-iterator-tracking branch September 22, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants