CVE-2025-31215
Overview
Background
- DFG/FTL abstract interpreter (AI)
- A dataflow analysis in JSC’s optimizing JIT that proves facts (types, structures) about values so later phases can drop checks and specialize code; its proofs must soundly match what the backend actually produces.
- NewArrayWithSpread / Spread
- DFG nodes that construct an array from spread elements (e.g. […x]); Spread produces the intermediate immutable butterfly and NewArrayWithSpread assembles the final array.
- JSImmutableButterfly / CopyOnWrite array
- A shared, immutable backing store (butterfly) used by copy-on-write arrays (CopyOnWriteArrayWithContiguous) so array literals can share storage until mutated.
- HavingABadTime watchpoint
- A JSGlobalObject watchpoint that fires when array prototype/indexed behavior is tampered with; while valid, the JIT may assume original, well-behaved array structures.
- PhantomNewArrayBuffer / PhantomCreateRest
- Phantom (materialization-elided) DFG nodes representing an array-literal buffer or a rest-parameter array that a Spread consumes without allocating the intermediate object.
- Structure
- JSC’s descriptor of an object’s shape and indexing type; JITted code specializes on structures, so a wrong structure assumption yields type confusion or out-of-bounds indexed access.
Root Cause Analysis
The bug is in JSC’s DFG abstract interpreter, specifically the AbstractInterpreter handling of the NewArrayWithSpread node in DFGAbstractInterpreterInlines.h. When compiling under the HavingABadTime watchpoint (i.e., array prototypes are unmodified) and the node has a single spread child whose bit is set, the FTL lowering (compileNewArrayWithSpread in FTLLowerDFGToB3.cpp) produces a CopyOnWrite contiguous array in essentially all sub-cases: a PhantomSpread of a PhantomNewArrayBuffer, a PhantomSpread of a PhantomCreateRest, or a directly-lowered immutable butterfly. The abstract interpreter, however, only proved the result was a CopyOnWrite original array (originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous)) for the narrow case where the spread wrapped a PhantomNewArrayBuffer with a contiguous indexing type; for every other single-child spread shape it fell through to a broader/less-precise structure set (originalArrayStructureForIndexingType(ArrayWithContiguous) plus the CopyOnWrite variant, or the non-original ArrayWithContiguous during allocation). This is an inconsistency between what the FTL backend actually allocates and what the abstract value model claims: the AI over- or mis-approximated the result structure relative to the concrete allocation, violating the invariant that the abstract interpreter’s proven structure must match (soundly and precisely) the structure the backend emits. Downstream optimizations that trust the AI’s structure set (for example CSE/structure checks and array-access lowering such as the .at() access in the test) can then operate on an array whose real structure differs from the assumed one.
The fix refactors compileNewArrayWithSpread so both the PhantomNewArrayBuffer and PhantomCreateRest cases go through shared helpers (createContiguousImmutableButterflyFromPhantomNewArrayBuffer / …FromPhantomCreateRest) that always yield a contiguous CopyOnWrite immutable butterfly, and correspondingly simplifies the AbstractInterpreter so that for the single-set-bit spread case it uniformly sets the node’s structure to originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous), and otherwise to arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous). This realigns the abstract structure with what is actually allocated, closing the mismatch. It is an inference (not shown verbatim in the diff) that the concrete downstream misuse is a type-confusion/OOB via an array-access node trusting the wrong structure; the diff shows the AI/lowering mismatch and a regression test that exercises spread + element write + out-of-range .at().
Attack Path
- Construct a single-child spread array literal Use a construct like […[1,2,3,4,5]] (PhantomNewArrayBuffer spread) or spreading rest arguments (PhantomCreateRest) so the DFG builds a NewArrayWithSpread node with one child whose bit vector bit 0 is set.
- Force FTL compilation under the good-time watchpoint Warm the enclosing function so it tiers up to FTL while the HavingABadTime watchpoint is still valid (the test uses –thresholdForFTLOptimizeAfterWarmUp=1000 and a 1000-iteration warmup with an inner counting loop).
- Trigger the AI/allocation structure mismatch In the spread shape not covered by the narrow AI case, the abstract interpreter models a structure set that does not match the CopyOnWrite contiguous array the FTL backend actually allocates.
- Feed the array to a structure-trusting operation Mutate and index the array (v10[2]=3 then v10.at(3745) in the test, plus a class extending a returned function to shape the graph) so an optimized array-access relies on the AI’s incorrect structure assumption.
- Provoke the fault The access proceeds under a wrong structure/indexing assumption, producing an out-of-bounds or type-confused access that crashes the WebContent process (the demonstrated outcome).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
forJSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js |
modified | |
C13JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js |
modified |
Files Changed
JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.jsSource/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.hSource/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Audit Directions
- Re-audit NewArrayWithSpread AI vs. FTL allocation parityIn DFGAbstractInterpreterInlines.h and FTLLowerDFGToB3.cpp, diff every branch of compileNewArrayWithSpread/compileSpread against the structure setForNode() calls; confirm originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous) vs arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous) is chosen consistently for each spread shape (PhantomSpread-of-PhantomNewArrayBuffer, PhantomSpread-of-PhantomCreateRest, direct immutable butterfly).
- Sweep other array-constructing nodes for the same AI/backend divergenceGrep DFGAbstractInterpreterInlines.h for setForNode paired with originalArrayStructureForIndexingType / arrayStructureForIndexingTypeDuringAllocation on NewArray, NewArrayBuffer, NewArrayWithSize, Spread, and CreateRest, and verify each matches the concrete structure used in the corresponding FTL/DFG lowering, especially where a hasContiguous()/indexingType() special case narrows only one branch.
- Check the refactored helpers for behavior changes, not just extractionCompare createContiguousImmutableButterflyFromPhantomCreateRest’s immutableButterflyStructures[arrayIndexFromIndexingType(CopyOnWriteArrayWithContiguous) - NumberOfIndexingShapes] against the original immutableButterflyStructure(CopyOnWriteArrayWithContiguous) call to ensure the structure selection is equivalent, and audit callers of these helpers (compileSpread and compileNewArrayWithSpread) for length/MAX_STORAGE_VECTOR_LENGTH and indexing-type assumptions.
- Look for HavingABadTime-gated structure assumptions elsewhereGrep for isWatchingHavingABadTimeWatchpoint together with structure/indexing-type selection across DFG/FTL; any place that proves an ‘original array’ structure in the AI under this watchpoint but branches its concrete allocation on indexingType()/hasContiguous() is a candidate for the same mismatch.
Patch
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
index 52dc45bfa761..78cd51b229cd 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
+++ b/Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
@@ -3594,7 +3594,8 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(node->indexingType()));
break;
- case NewArrayWithSpread:
+ case NewArrayWithSpread: {
+ auto* globalObject = m_graph.globalObjectFor(node->origin.semantic);
if (m_graph.isWatchingHavingABadTimeWatchpoint(node)) {
// We've compiled assuming we're not having a bad time, so to be consistent
// with StructureRegisterationPhase we must say we produce an original array
@@ -3602,29 +3603,14 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
#if USE(JSVALUE64)
BitVector* bitVector = node->bitVector();
if (node->numChildren() == 1 && bitVector->get(0)) {
- Edge use = m_graph.varArgChild(node, 0);
- if (use->op() == PhantomSpread) {
- if (use->child1()->op() == PhantomNewArrayBuffer) {
- auto* immutableButterfly = use->child1()->castOperand<JSImmutableButterfly*>();
- if (hasContiguous(immutableButterfly->indexingType())) {
- setForNode(node, m_graph.globalObjectFor(node->origin.semantic)->originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous));
- break;
- }
- }
- }
+ setForNode(node, globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous));
+ break;
}
#endif
- JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
- RegisteredStructureSet structureSet;
- structureSet.add(m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(ArrayWithContiguous)));
- structureSet.add(m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous)));
- setForNode(node, structureSet);
- } else {
- setForNode(node,
- m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous));
}
-
+ setForNode(node, globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous));
break;
+ }
case Spread:
switch (node->child1()->op()) {
diff --git a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
index fee6e1b55eba..2b1ec8f74135 100644
--- a/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
+++ b/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
@@ -9547,53 +9547,37 @@ IGNORE_CLANG_WARNINGS_END
if (m_node->numChildren() == 1 && bitVector->get(0)) {
Edge use = m_graph.varArgChild(m_node, 0);
+ LValue immutableButterfly = nullptr;
if (use->op() == PhantomSpread) {
- if (use->child1()->op() == PhantomNewArrayBuffer) {
- auto* immutableButterfly = use->child1()->castOperand<JSImmutableButterfly*>();
- if (hasContiguous(immutableButterfly->indexingType())) {
- RegisteredStructure structure = m_graph.registerStructure(globalObject->originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous));
- LBasicBlock slowPath = m_out.newBlock();
- LBasicBlock continuation = m_out.newBlock();
-
- LValue fastArray = allocateObject<JSArray>(structure, m_out.constIntPtr(immutableButterfly->toButterfly()), slowPath);
- ValueFromBlock fastResult = m_out.anchor(fastArray);
- m_out.jump(continuation);
-
- m_out.appendTo(slowPath, continuation);
- LValue slowArray = vmCall(Int64, operationNewArrayBuffer, m_vmValue, weakStructure(structure), frozenPointer(use->child1()->cellOperand()));
- ValueFromBlock slowResult = m_out.anchor(slowArray);
- m_out.jump(continuation);
-
- m_out.appendTo(continuation);
-
- mutatorFence();
- setJSValue(m_out.phi(pointerType(), slowResult, fastResult));
- return;
- }
+ if (use->child1()->op() == PhantomNewArrayBuffer)
+ immutableButterfly = createContiguousImmutableButterflyFromPhantomNewArrayBuffer(globalObject, use->child1().node());
+ else {
+ RELEASE_ASSERT(use->child1()->op() == PhantomCreateRest);
+ immutableButterfly = createContiguousImmutableButterflyFromPhantomCreateRest(globalObject, use->child1().node());
}
} else {
// If a node is producing JSImmutableButterfly, it must be contiguous.
- LValue immutableButterfly = lowCell(use);
+ immutableButterfly = lowCell(use);
+ }
- RegisteredStructure structure = m_graph.registerStructure(globalObject->arrayStructureForIndexingTypeDuringAllocation(CopyOnWriteArrayWithContiguous));
- LBasicBlock slowPath = m_out.newBlock();
- LBasicBlock continuation = m_out.newBlock();
+ RegisteredStructure structure = m_graph.registerStructure(globalObject->arrayStructureForIndexingTypeDuringAllocation(CopyOnWriteArrayWithContiguous));
+ LBasicBlock slowPath = m_out.newBlock();
+ LBasicBlock continuation = m_out.newBlock();
- LValue fastArray = allocateObject<JSArray>(structure, toButterfly(immutableButterfly), slowPath);
- ValueFromBlock fastResult = m_out.anchor(fastArray);
- m_out.jump(continuation);
+ LValue fastArray = allocateObject<JSArray>(structure, toButterfly(immutableButterfly), slowPath);
+ ValueFromBlock fastResult = m_out.anchor(fastArray);
+ m_out.jump(continuation);
- m_out.appendTo(slowPath, continuation);
- LValue slowArray = vmCall(Int64, operationNewArrayBuffer, m_vmValue, weakStructure(structure), immutableButterfly);
- ValueFromBlock slowResult = m_out.anchor(slowArray);
- m_out.jump(continuation);
+ m_out.appendTo(slowPath, continuation);
+ LValue slowArray = vmCall(Int64, operationNewArrayBuffer, m_vmValue, weakStructure(structure), immutableButterfly);
+ ValueFromBlock slowResult = m_out.anchor(slowArray);
+ m_out.jump(continuation);
- m_out.appendTo(continuation);
+ m_out.appendTo(continuation);
- mutatorFence();
- setJSValue(m_out.phi(pointerType(), slowResult, fastResult));
- return;
- }
+ mutatorFence();
+ setJSValue(m_out.phi(pointerType(), slowResult, fastResult));
+ return;
}
for (unsigned i = 0; i < m_node->numChildren(); ++i) {
@@ -9933,50 +9917,103 @@ IGNORE_CLANG_WARNINGS_END
compileCreateInternalFieldObject<JSAsyncGenerator>(operationCreateAsyncGenerator);
}
- void compileSpread()
+ LValue createContiguousImmutableButterflyFromPhantomNewArrayBuffer(JSGlobalObject* globalObject, Node* newArrayBufferNode)
{
- JSGlobalObject* globalObject = m_graph.globalObjectFor(m_origin.semantic);
- if (m_node->child1()->op() == PhantomNewArrayBuffer) {
- ASSERT(m_graph.isWatchingHavingABadTimeWatchpoint(m_node->child1().node()));
+ ASSERT(newArrayBufferNode->op() == PhantomNewArrayBuffer);
+ auto* immutableButterfly = newArrayBufferNode->castOperand<JSImmutableButterfly*>();
+ if (hasContiguous(immutableButterfly->indexingType()))
+ return frozenPointer(newArrayBufferNode->cellOperand());
- // FIXME: JSImmutableButterfly::createFromArray should support re-using non contiguous indexing types as well.
- auto* immutableButterfly = m_node->child1()->castOperand<JSImmutableButterfly*>();
- if (hasContiguous(immutableButterfly->indexingType())) {
- setJSValue(frozenPointer(m_node->child1()->cellOperand()));
- return;
- }
+ LBasicBlock slowAllocation = m_out.newBlock();
+ LBasicBlock continuation = m_out.newBlock();
- LBasicBlock slowAllocation = m_out.newBlock();
- LBasicBlock continuation = m_out.newBlock();
+ ASSERT(immutableButterfly->length() <= MAX_STORAGE_VECTOR_LENGTH);
- ASSERT(immutableButterfly->length() <= MAX_STORAGE_VECTOR_LENGTH);
+ LValue fastImmutableButterflyValue = allocateVariableSizedCell<JSImmutableButterfly>(
+ m_out.constIntPtr(JSImmutableButterfly::allocationSize(immutableButterfly->length())),
+ m_graph.m_vm.immutableButterflyStructure(CopyOnWriteArrayWithContiguous), slowAllocation);
+ LValue fastImmutableButterflyStorage = toButterfly(fastImmutableButterflyValue);
+ m_out.store32(m_out.constInt32(immutableButterfly->length()), fastImmutableButterflyStorage, m_heaps.Butterfly_publicLength);
+ m_out.store32(m_out.constInt32(immutableButterfly->length()), fastImmutableButterflyStorage, m_heaps.Butterfly_vectorLength);
+ ValueFromBlock fastImmutableButterfly = m_out.anchor(fastImmutableButterflyValue);
+ m_out.jump(continuation);
- LValue fastImmutableButterflyValue = allocateVariableSizedCell<JSImmutableButterfly>(
- m_out.constIntPtr(JSImmutableButterfly::allocationSize(immutableButterfly->length())),
- m_graph.m_vm.immutableButterflyStructure(CopyOnWriteArrayWithContiguous), slowAllocation);
- LValue fastImmutableButterflyStorage = toButterfly(fastImmutableButterflyValue);
- m_out.store32(m_out.constInt32(immutableButterfly->length()), fastImmutableButterflyStorage, m_heaps.Butterfly_publicLength);
- m_out.store32(m_out.constInt32(immutableButterfly->length()), fastImmutableButterflyStorage, m_heaps.Butterfly_vectorLength);
- ValueFromBlock fastImmutableButterfly = m_out.anchor(fastImmutableButterflyValue);
- m_out.jump(continuation);
+ LBasicBlock lastNext = m_out.appendTo(slowAllocation, continuation);
+ ValueFromBlock slowImmutableButterfly = m_out.anchor(vmCall(pointerType(), operationCreateImmutableButterfly, weakPointer(globalObject), m_out.constInt32(immutableButterfly->length())));
+ m_out.jump(continuation);
- LBasicBlock lastNext = m_out.appendTo(slowAllocation, continuation);
- ValueFromBlock slowImmutableButterfly = m_out.anchor(vmCall(pointerType(), operationCreateImmutableButterfly, weakPointer(globalObject), m_out.constInt32(immutableButterfly->length())));
- m_out.jump(continuation);
+ m_out.appendTo(continuation, lastNext);
+ LValue immutableButterflyValue = m_out.phi(pointerType(), fastImmutableButterfly, slowImmutableButterfly);
+ LValue immutableButterflyStorage = toButterfly(immutableButterflyValue);
+ for (unsigned i = 0; i < immutableButterfly->length(); i++) {
+ // Because forwarded values are drained as JSValue, we should not generate value
+ // in Double form even if PhantomNewArrayBuffer's indexingType is ArrayWithDouble.
+ int64_t value = JSValue::encode(immutableButterfly->get(i));
+ m_out.store64(
+ m_out.constInt64(value),
+ m_out.baseIndex(m_heaps.indexedContiguousProperties, immutableButterflyStorage, m_out.constIntPtr(i), jsNumber(i)));
+ }
+ mutatorFence();
+ return immutableButterflyValue;
+ }
- m_out.appendTo(continuation, lastNext);
- LValue immutableButterflyValue = m_out.phi(pointerType(), fastImmutableButterfly, slowImmutableButterfly);
- LValue immutableButterflyStorage = toButterfly(immutableButterflyValue);
- for (unsigned i = 0; i < immutableButterfly->length(); i++) {
- // Because forwarded values are drained as JSValue, we should not generate value
- // in Double form even if PhantomNewArrayBuffer's indexingType is ArrayWithDouble.
- int64_t value = JSValue::encode(immutableButterfly->get(i));
- m_out.store64(
- m_out.constInt64(value),
- m_out.baseIndex(m_heaps.indexedContiguousProperties, immutableButterflyStorage, m_out.constIntPtr(i), jsNumber(i)));
- }
- mutatorFence();
- setJSValue(immutableButterflyValue);
+ LValue createContiguousImmutableButterflyFromPhantomCreateRest(JSGlobalObject* globalObject, Node* restNode)
+ {
+ ASSERT(restNode->op() == PhantomCreateRest);
+ LBasicBlock fastAllocation = m_out.newBlock();
+ LBasicBlock loopHeader = m_out.newBlock();
+ LBasicBlock loopBody = m_out.newBlock();
+ LBasicBlock slowAllocation = m_out.newBlock();
+ LBasicBlock continuation = m_out.newBlock();
+
+ InlineCallFrame* inlineCallFrame = restNode->origin.semantic.inlineCallFrame();
+ unsigned numberOfArgumentsToSkip = restNode->numberOfArgumentsToSkip();
+ LValue sourceStart = getArgumentsStart(inlineCallFrame, numberOfArgumentsToSkip);
+ LValue length = getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip);
+ static_assert(sizeof(JSValue) == 8 && 1 << 3 == 8, "Assumed in the code below.");
+ LValue size = m_out.add(
+ m_out.shl(m_out.zeroExtPtr(length), m_out.constInt32(3)),
+ m_out.constIntPtr(JSImmutableButterfly::offsetOfData()));
+ m_out.branch(m_out.above(length, m_out.constInt32(MAX_STORAGE_VECTOR_LENGTH)), rarely(slowAllocation), usually(fastAllocation));
+
+ LBasicBlock lastNext = m_out.appendTo(fastAllocation, slowAllocation);
+ LValue fastArrayValue = allocateVariableSizedCell<JSImmutableButterfly>(size, m_graph.m_vm.immutableButterflyStructures[arrayIndexFromIndexingType(CopyOnWriteArrayWithContiguous) - NumberOfIndexingShapes].get(), slowAllocation);
+ LValue fastArrayStorage = toButterfly(fastArrayValue);
+ m_out.store32(length, fastArrayStorage, m_heaps.Butterfly_vectorLength);
+ m_out.store32(length, fastArrayStorage, m_heaps.Butterfly_publicLength);
+ ValueFromBlock fastArray = m_out.anchor(fastArrayValue);
+ m_out.jump(loopHeader);
+
+ m_out.appendTo(slowAllocation, loopHeader);
+ ValueFromBlock slowArray = m_out.anchor(vmCall(pointerType(), operationCreateImmutableButterfly, weakPointer(globalObject), length));
+ m_out.jump(loopHeader);
+
+ m_out.appendTo(loopHeader, loopBody);
+ LValue immutableButterfly = m_out.phi(pointerType(), fastArray, slowArray);
+ LValue immutableButterflyStorage = toButterfly(immutableButterfly);
+ ValueFromBlock startIndex = m_out.anchor(m_out.constIntPtr(0));
+ m_out.branch(m_out.isZero32(length), unsure(continuation), unsure(loopBody));
+
+ m_out.appendTo(loopBody, continuation);
+ LValue index = m_out.phi(pointerType(), startIndex);
+ LValue value = m_out.load64(m_out.baseIndex(m_heaps.variables, sourceStart, index));
+ m_out.store64(value, m_out.baseIndex(m_heaps.indexedContiguousProperties, immutableButterflyStorage, index));
+ LValue nextIndex = m_out.add(m_out.constIntPtr(1), index);
+ m_out.addIncomingToPhi(index, m_out.anchor(nextIndex));
+ m_out.branch(m_out.below(nextIndex, m_out.zeroExtPtr(length)), unsure(loopBody), unsure(continuation));
+
+ m_out.appendTo(continuation, lastNext);
+ mutatorFence();
+ return immutableButterfly;
+ }
+
+ void compileSpread()
+ {
+ JSGlobalObject* globalObject = m_graph.globalObjectFor(m_origin.semantic);
+ if (m_node->child1()->op() == PhantomNewArrayBuffer) {
+ ASSERT(m_graph.isWatchingHavingABadTimeWatchpoint(m_node->child1().node()));
+ // FIXME: JSImmutableButterfly::createFromArray should support re-using non contiguous indexing types as well.
+ setJSValue(createContiguousImmutableButterflyFromPhantomNewArrayBuffer(globalObject, m_node->child1().node()));
return;
}
@@ -9988,53 +10025,7 @@ IGNORE_CLANG_WARNINGS_END
// the Spread but nothing escapes the CreateRest.
ASSERT(m_graph.isWatchingHavingABadTimeWatchpoint(m_node->child1().node()));
-
- LBasicBlock fastAllocation = m_out.newBlock();
- LBasicBlock loopHeader = m_out.newBlock();
- LBasicBlock loopBody = m_out.newBlock();
- LBasicBlock slowAllocation = m_out.newBlock();
- LBasicBlock continuation = m_out.newBlock();
-
- InlineCallFrame* inlineCallFrame = m_node->child1()->origin.semantic.inlineCallFrame();
- unsigned numberOfArgumentsToSkip = m_node->child1()->numberOfArgumentsToSkip();
- LValue sourceStart = getArgumentsStart(inlineCallFrame, numberOfArgumentsToSkip);
- LValue length = getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip);
- static_assert(sizeof(JSValue) == 8 && 1 << 3 == 8, "Assumed in the code below.");
- LValue size = m_out.add(
- m_out.shl(m_out.zeroExtPtr(length), m_out.constInt32(3)),
- m_out.constIntPtr(JSImmutableButterfly::offsetOfData()));
- m_out.branch(m_out.above(length, m_out.constInt32(MAX_STORAGE_VECTOR_LENGTH)), rarely(slowAllocation), usually(fastAllocation));
-
- LBasicBlock lastNext = m_out.appendTo(fastAllocation, slowAllocation);
- LValue fastArrayValue = allocateVariableSizedCell<JSImmutableButterfly>(size, m_graph.m_vm.immutableButterflyStructure(CopyOnWriteArrayWithContiguous), slowAllocation);
- LValue fastArrayStorage = toButterfly(fastArrayValue);
- m_out.store32(length, fastArrayStorage, m_heaps.Butterfly_vectorLength);
- m_out.store32(length, fastArrayStorage, m_heaps.Butterfly_publicLength);
- ValueFromBlock fastArray = m_out.anchor(fastArrayValue);
- m_out.jump(loopHeader);
-
- m_out.appendTo(slowAllocation, loopHeader);
- ValueFromBlock slowArray = m_out.anchor(vmCall(pointerType(), operationCreateImmutableButterfly, weakPointer(globalObject), length));
- m_out.jump(loopHeader);
-
- m_out.appendTo(loopHeader, loopBody);
- LValue immutableButterfly = m_out.phi(pointerType(), fastArray, slowArray);
- LValue immutableButterflyStorage = toButterfly(immutableButterfly);
- ValueFromBlock startIndex = m_out.anchor(m_out.constIntPtr(0));
- m_out.branch(m_out.isZero32(length), unsure(continuation), unsure(loopBody));
-
- m_out.appendTo(loopBody, continuation);
- LValue index = m_out.phi(pointerType(), startIndex);
- LValue value = m_out.load64(
- m_out.baseIndex(m_heaps.variables, sourceStart, index));
- m_out.store64(value, m_out.baseIndex(m_heaps.indexedContiguousProperties, immutableButterflyStorage, index));
- LValue nextIndex = m_out.add(m_out.constIntPtr(1), index);
- m_out.addIncomingToPhi(index, m_out.anchor(nextIndex));
- m_out.branch(m_out.below(nextIndex, m_out.zeroExtPtr(length)), unsure(loopBody), unsure(continuation));
-
- m_out.appendTo(continuation, lastNext);
- mutatorFence();
- setJSValue(immutableButterfly);
+ setJSValue(createContiguousImmutableButterflyFromPhantomCreateRest(globalObject, m_node->child1().node()));
return;
}
diff --git a/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js b/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js
new file mode 100644
index 000000000000..5f1699bb4ccb
--- /dev/null
+++ b/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js
@@ -0,0 +1,11 @@
+//@ runDefault("--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=0")
+for (let i = 0; i < 1000; ++i) {
+ (() => {
+ for (let v8 = 0; v8 < 25; v8++) {}
+ const v10 = [...[1,2,3,4,5]];
+ v10[2] = 3;
+ function f11(a12) { return a12;}
+ class C13 extends f11 {}
+ v10.at(3745);
+ })();
+}
Regression Test / PoC
diff --git a/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js b/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js
new file mode 100644
index 000000000000..5f1699bb4ccb
--- /dev/null
+++ b/JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js
@@ -0,0 +1,11 @@
+//@ runDefault("--thresholdForFTLOptimizeAfterWarmUp=1000", "--useConcurrentJIT=0")
+for (let i = 0; i < 1000; ++i) {
+ (() => {
+ for (let v8 = 0; v8 < 25; v8++) {}
+ const v10 = [...[1,2,3,4,5]];
+ v10[2] = 3;
+ function f11(a12) { return a12;}
+ class C13 extends f11 {}
+ v10.at(3745);
+ })();
+}