DFG ArrayShift intrinsic node
The DFG bytecode parser can replace a generic JS function call with a hand-crafted node that the backend lowers to tightly optimised machine code. Each new intrinsic must be integrated into every phase that touches nodes: type fixup, clobber analysis, abstract interpretation, GC-interaction tracking, and the per-backend code generators. Array.shift() is semantically more complex than Array.pop() because it removes from the front: for length > 1 a full element-shift is required.
This commit adds ArrayShift as a new DFG intrinsic node, wired through the full DFG/FTL pipeline (fixup, clobberize, abstract interpreter, B3 lowering). The inline fast path covers only arrays of length 0 or 1; arrays of length ≥ 2 fall through to operationArrayShift at runtime. The fast path reads storage[0], stores empty over it, and decrements publicLength, bypassing the prototype-chain machinery.
Significance
This adds JIT-compiled machine code for a heavily-used array operation that mutates the front of the storage — a previously C++-only path now executing as speculative machine code with its own GC interaction and OSR-exit boundaries.
Audit directions
- Hole detection on the fast path. The length-1 fast path must detect an empty slot in
storage[0]and bail to the slow path. If the emptiness check is incorrect or the array-mode speculation is wrong, a stale or uninitialised value could be returned to JS — potential type confusion. - GC interaction window. The fast path reads
storage[0], storesemptyover it, then decrementspublicLength. IfdoesGCmisclassifies this node as GC-free in a path that can actually trigger allocation, the GC could observe a partially-updated butterfly. - FTL
compileArrayShift. Brand-new B3 lowering — verify it correctly handles all speculated array modes (Int32, Double, Contiguous, ArrayStorage) and thepublicLengthwrite is correctly fenced relative to the value read. - Speculation vs deopt boundary. Unlike
ArrayPop,ArrayShiftchanges the front of the array. If a prototype has a numeric setter at index 0 and the speculative check misses it, the observable mutation could be skipped silently. - Length 1 → 0 transition. After the fast path,
publicLengthbecomes 0 withstorage[0]zeroed out. Any code that observes the array between the element clear and the length decrement sees an inconsistent state.