← All issues

JSC ArrayLengthStore inline cache for JSArray length assignment

da43fc7

JSTests/stress/array-length-store-ic-warm-then-freeze-strict.js

+ function f(a, n) { 'use strict'; a.length = n; }
+ noInline(f);
+ let a = [1, 2, 3];
+ for (let i = 0; i < testLoopCount; ++i)
+ f(a, 2); // warms IC to ArrayLengthStore
+ Object.freeze(a);
+ shouldThrow(() => f(a, 1), TypeError); // IC must not misfire on frozen array

JSTests/stress/array-length-store-ic-cow.js

+ function f(a, n) { a.length = n; }
+ noInline(f);
+ for (let i = 0; i < testLoopCount; ++i) {
+ let a = mk(); // returns a COW array literal
+ f(a, 1);
+ shouldBe(a.length, 1);
+ }

JSC's inline cache (IC) system caches property operations as type-specialized machine-code stubs. When the JIT encounters a property access, it builds a stub keyed on the object's structure ID so that subsequent accesses hit the stub directly, bypassing the generic slow path. The length property of JSArray is special — it is not a normal stored property but a "custom value" whose setter (JSArray::setLength) enforces invariants across five array storage modes (Undecided, Int32, Contiguous, Double, ArrayStorage/SlowPutArrayStorage). Previously, JSArray::put left the PutPropertySlot uncacheable for length, so every arr.length = N assignment fell through to operationPutByIdStrictGaveUp — the generic slow path — on every call.

This commit adds AccessCase::ArrayLengthStore, a new IC type that handles the common shrink case inline. For arrays with Int32Shape or ContiguousShape, when the new length is a non-negative int32 no greater than the current publicLength, the emitted stub clears the tail slots in the butterfly storage and updates publicLength directly. All other cases — grow, ArrayStorage mode, non-integer length, COW arrays — fall back to the slow path.

Before:
  arr.length = N
    └─► putById ──► tryCachePutBy ──► (uncacheable, gives up)
                                            └─► operationPutByIdStrictGaveUp
                                                    └─► JSArray::put (every call)

After:
  arr.length = N
    └─► putById ──► IC lookup
                      ├─ [ArrayLengthStore stub: Int32/Contiguous, newLen ≤ publicLen]
                      │       └─► zero tail slots ──► update publicLength  (fast, inline)
                      └─ [miss / grow / ArrayStorage / non-int]
                              └─► operationPutByIdStrictGaveUp ──► JSArray::put (slow)

The result is a 3.5–4.3x speedup in DFG/FTL tiers for the push-then-reset idiom, which is common in pool-based allocation patterns where arrays are reused by truncating length to zero each iteration. The speedup comes from eliminating the full JSArray::putsetLength path on the hot shrink case, replacing it with a handful of inline instructions that zero slots and store a length.

🔒

New JIT stubs directly manipulate array storage — several fast-path guard interactions and object lifecycle edge cases are worth security investigation.

Subscribe to read more