← All issues

JSC `ArrayIsArray` DFG Intrinsic

b7c9035

Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp

+void SpeculativeJIT::compileArrayIsArray(Node* node)
+{
+ JSValueOperand value(this, node->child1());
+ GPRTemporary result(this);
+ JSValueRegs valueRegs = value.jsValueRegs();
+ GPRReg resultGPR = result.gpr();
+
+ Jump isNotCell = branchIfNotCell(valueRegs);
+ static_assert(DerivedArrayType == ArrayType + 1, ...);
+ load8(Address(valueRegs.payloadGPR(), JSCell::typeInfoTypeOffset()), resultGPR);
+ sub32(TrustedImm32(ArrayType), resultGPR);
+ Jump isArrayOrDerived = branch32(BelowOrEqual, resultGPR, TrustedImm32(DerivedArrayType - ArrayType));
+ Jump isProxy = branch32(Equal, resultGPR, TrustedImm32(ProxyObjectType - ArrayType));
+ isNotCell.link(this);
+ move(TrustedImm32(0), resultGPR);
+ Jump done = jump();
+ isArrayOrDerived.link(this);
+ move(TrustedImm32(1), resultGPR);
+ addSlowPathGenerator(slowPathCall(isProxy, this, operationArrayIsArray, resultGPR,
+ LinkableConstant::globalObject(*this, node), valueRegs));
+ done.link(this);
+ unblessedBooleanResult(resultGPR, node);
+}

JSC's inline cache system speculatively compiles fast property access stubs based on observed object shapes. The previous Array.isArray implementation was a JS builtin backed by IsCellWithType(CellUse), which told the DFG speculative compiler to assume the input is always a GC-managed cell. The TypeCheckHoistingPhase then moved this cell check to function entry — meaning a single non-cell call (undefined, null, a boolean) was enough to trigger a BadType OSR exit and jettison the entire compiled code unit. Hot code mixing typed and untyped inputs was silently deoptimizing.

Before:
  call Array.isArray(v)
    └─► JS builtin → IsCellWithType(CellUse)
          └─► TypeCheckHoistingPhase hoists cell check to entry
                └─► v = undefined / null / boolean → BadType OSR exit → DFG jettison

After:
  call Array.isArray(v)
    └─► ArrayIsArray DFG node (UntypedUse)
          ├── non-cell (int/double/bool/null/undefined) ──► false (inline)
          ├── ArrayType / DerivedArrayType             ──► true  (inline)
          ├── ProxyObjectType                          ──► isArraySlow() (C++ slow path)
          └── other cell                               ──► false (inline)

This commit replaces the builtin with a C++ host function backed by a new ArrayIsArray DFG/FTL IR node using UntypedUse. The node handles all ES spec cases inline: non-cell → false, ArrayType/DerivedArrayType → true, ProxyObjectType → slow path, other cells → false. The abstract interpreter can constant-fold it when the input type is statically known.

The result eliminates deoptimization entirely for the common case of Array.isArray called with mixed types, while retaining constant-folding for the known-array case. The range check technique — subtracting ArrayType and using BelowOrEqual with DerivedArrayType - ArrayType — is compact but relies on the static_assert that these JSType constants are contiguous.

🔒

Pattern-based audit directions for variant discovery

Subscribe to read more