for-in over arrays: enumerate dense indices lazily without materializing a key list#2656
Merged
Merged
Conversation
lahma
enabled auto-merge (squash)
July 13, 2026 08:04
…ing a key list QuickJS-style fast path: a dense JsArray with no materialized per-index descriptors, no named own props, and a prototype chain that provably contributes no enumerable string keys enumerates its present indices directly - no per-enumeration List allocation, no numeric sort, no per-step string->index parse in ProbeOwnProperty, and (indices < 1024) no per-key JsString allocation. When the index range is exhausted the enumeration is complete; anything else - extra named own props, sparse arrays, dirty chains, proxies, or mid-loop deopts - takes the existing exact snapshot path, and mid-loop representation changes fall back to per-index probing that honors live enumerability. The chain precheck introduces HasNoEnumerableOwnStringKeys: a conservative virtual (default false) with exact overrides for plain objects, Object.prototype and array-backed hosts, backed by a precomputed all-fixed-slots-non-enumerable bit on BuiltinShape plus a live scan of materialized descriptors and hybrid additions. ForInGuardBenchmark (default job): | Lane | Before | After | |--------------------- |--------------------:|--------------------:| | ForInDenseArray | 14.49 ms / 1,620 KB | 11.96 ms / 1.32 KB | | ForInHoleyArray | 3.82 ms / 1,620 KB | 3.07 ms / 1.32 KB | | ForInArrayExtraProps | 16.98 ms / 2,470 KB | 16.68 ms / 2,470 KB | -17.5%/-19.7% time and -99.9% allocation on the array lanes; object receivers and the legacy-path array lane are unchanged (A/B/A confirmed the small ForInSmall delta as ambient drift). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
force-pushed
the
perf-quickjs-ws1-forin-array
branch
from
July 13, 2026 08:06
442c51c to
e4ffa37
Compare
Collaborator
Author
This was referenced Jul 13, 2026
This was referenced Jul 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second PR of the quickjs-ng learnings campaign (lanes: #2654). Stacked on #2654 and #2655 — the first two commits are those PRs; review the last commit. Will rebase as they merge.
What
for (var k in arr)currently materializes a full key list on every enumeration: a length-presizedList<JsValue>, aJsStringper index, a numeric-order pass — then re-probes each key throughProbeOwnProperty, which parses the string back to an index per step.QuickJS'''s
build_for_in_iteratorhas a fast path for this: when the receiver is a fast array and the prototype chain contributes no enumerable keys, it just records the length and yields indices lazily. This PR ports that idea:JsArraywith no materialized per-index descriptors and no named own props, whose prototype chain provably has no enumerable string keys, enumerates its present indices directly. Indices < 1024 come from the shared numeric-string cache, so the enumeration allocates nothing.definePropertymaterializing descriptors) are picked up per step and fall back to per-index probing that honors live enumerability.The chain precheck introduces
HasNoEnumerableOwnStringKeys(): a conservative virtual (defaultfalse= "cannot prove it, take the exact path") with exact overrides for plain objects,Object.prototypeand array-backed hosts, backed by a precomputed all-fixed-slots-non-enumerable bit onBuiltinShapeplus a live scan of materialized descriptors and hybrid additions. A wrongtruewould drop keys, so exotic key providers never opt in.Numbers
ForInGuardBenchmark, default job:
Tests
14 new enumeration tests pinning the fast path'''s edges: holey arrays, empty-with-large-length, delete/push/length-truncation mid-loop,
definePropertymid-loop honoring new enumerability, dense→sparse conversion mid-loop, dirty chains viaArray.prototypeindices andObject.prototypeadditions, array subclasses, nested/generator-interleaved enumerations, pooled-iterator reuse afterbreak.Full gate green: Jint.Tests, PublicInterface, CommonScripts, and a clean 99,429-test Test262 run.
🤖 Generated with Claude Code