You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After #8655, Perry can hit an inline packed-arraylike path for Array-subclass numeric reads, but it still repeats the full representation proof inside the entity loop. Stable ECS iteration therefore pays header, kind, forwarding, version, bounds, inline/spill, prototype, and fallback checks for every element instead of proving the loop once.
The remaining optimization is general loop versioning: enter a guarded private packed loop from a preheader, execute direct indexed loads in the fast arm, and transfer to the generic loop at the first semantically safe index if a proof changes.
Typed-array read-modify-write is tracked separately in #8692; this issue owns the source Array/Array-subclass iteration and guard placement.
Self-contained reproduction
This read-only variant isolates packed Array-subclass iteration from component TypedArrays:
The original #8655 reproduction should remain a second integration fixture because it matches wolf-ecs and has a stable before/after cohort.
Current evidence
On Perry 7ad718ab4287641cb2b29dce3a056edc45d4c7f8, the original #8655 reproduction improved from 445.038 ms to 70.496 ms, but remains 31.76x slower than Node on an M1 Mac mini.
Fresh LLVM inspection shows:
query[i] still calls js_object_get_index_polymorphic after object coercibility and symbol/string/numeric dispatch.
current.length uses a generic PIC/property path with repeated class/string/null checks.
current[j] has an inline packed-arraylike arm, but repeats header validity, array classification, element kind, forwarding/version, bounds, and inline/spill checks for every entity, with js_packed_arraylike_index_get as fallback.
GC poll/root/barrier checks remain in the inner loop even when the fast arm cannot allocate or call arbitrary user code.
The unchanged codehz/ecs query profile shows the same architectural ceiling: after packed method-shape guard work, Perry remains 2.897x slower than Node for accumulation and 6.125x for read-only iteration. The dominant accumulation subtree is repeated method/array proof inside Archetype.forEachWithComponents, not one remaining header-load micro-optimization.
Proposed direction
Recognize general counted loops over exact packed Array or Array-subclass inputs with a stable element representation.
Emit a preheader that proves receiver/class/shape, descriptor state, prototype/indexed-accessor state, forwarding/version, element kind, holes policy, length, and backing storage once.
Enter a private fast loop containing direct length/indexed loads and only the minimal checks required by calls or safepoints inside the body.
If the body can mutate the receiver, prototype, or aliased arrays, validate sticky generations after the mutation point and side-exit to the generic loop at the first safe index.
Refresh relocated roots after possible collection without discarding unrelated stable proofs.
Keep the generic loop as an explicit fallback; do not bake in ECS class names, method names, source locations, or fixed loop sizes.
Semantic constraints
Preserve holes, inherited indexed accessors, proxies, species/subclass behavior where relevant, descriptors, prototype mutation, array growth/shrink, element-kind transitions, and exception order.
Preserve JavaScript for loop ordering if length, the indexed getter, or the body has side effects.
A callback capable of mutating the same array through an alias must either force fallback or trigger safe revalidation.
Remain correct when arrays move or grow under forced GC; forwarding compression alone is not a proof that a cached pointer stays valid.
The read-only fast loop contains no js_object_get_index_polymorphic or js_packed_arraylike_index_get, and does not repeat full array classification/shape/prototype checks per element.
Native-region artifacts identify the preheader proof, minimal in-loop revalidations, and generic side exit.
Mutation tests cover holes, indexed prototype accessors, prototype replacement, descriptors, proxies, growth/shrink, element-kind changes, aliasing from the loop body/callback, thrown getters/callbacks, and forced-moving GC.
On the quiet M1 alternating protocol, improve the isolated read-only reproduction by at least 2x and the unchanged ECS accumulation row by at least 2%, each with at least 9/11 paired wins.
Re-run the unchanged codehz/ecs suite and noctjs/ecs-benchmarkwolf-ecs/simple_iter, reporting Node/Perry medians, semantic checksums, RSS, and executable-size changes.
Summary
After #8655, Perry can hit an inline packed-arraylike path for Array-subclass numeric reads, but it still repeats the full representation proof inside the entity loop. Stable ECS iteration therefore pays header, kind, forwarding, version, bounds, inline/spill, prototype, and fallback checks for every element instead of proving the loop once.
The remaining optimization is general loop versioning: enter a guarded private packed loop from a preheader, execute direct indexed loads in the fast arm, and transfer to the generic loop at the first semantically safe index if a proof changes.
Typed-array read-modify-write is tracked separately in #8692; this issue owns the source Array/Array-subclass iteration and guard placement.
Self-contained reproduction
This read-only variant isolates packed Array-subclass iteration from component TypedArrays:
Build and retain LLVM/lowering evidence:
The original #8655 reproduction should remain a second integration fixture because it matches
wolf-ecsand has a stable before/after cohort.Current evidence
On Perry
7ad718ab4287641cb2b29dce3a056edc45d4c7f8, the original #8655 reproduction improved from 445.038 ms to 70.496 ms, but remains 31.76x slower than Node on an M1 Mac mini.Fresh LLVM inspection shows:
query[i]still callsjs_object_get_index_polymorphicafter object coercibility and symbol/string/numeric dispatch.current.lengthuses a generic PIC/property path with repeated class/string/null checks.current[j]has an inline packed-arraylike arm, but repeats header validity, array classification, element kind, forwarding/version, bounds, and inline/spill checks for every entity, withjs_packed_arraylike_index_getas fallback.The unchanged
codehz/ecsquery profile shows the same architectural ceiling: after packed method-shape guard work, Perry remains 2.897x slower than Node for accumulation and 6.125x for read-only iteration. The dominant accumulation subtree is repeated method/array proof insideArchetype.forEachWithComponents, not one remaining header-load micro-optimization.Proposed direction
Semantic constraints
forloop ordering iflength, the indexed getter, or the body has side effects.Acceptance criteria
js_object_get_index_polymorphicorjs_packed_arraylike_index_get, and does not repeat full array classification/shape/prototype checks per element.codehz/ecssuite andnoctjs/ecs-benchmarkwolf-ecs/simple_iter, reporting Node/Perry medians, semantic checksums, RSS, and executable-size changes.Related work