Skip to content

Commit dfe5dc6

Browse files
committed
[JSC] Implement ArrayShift DFG node
https://bugs.webkit.org/show_bug.cgi?id=314926 rdar://177210625 Reviewed by Yijia Huang. This patch implements ArrayShift DFG node. This is mostly the same to ArrayPop, but we handle inline only when array.length is 0 or 1. Other than that, basically the same to ArrayPop. We load value for returned value, and store empty value for that and then updating publicLength. When returned value is empty, we need much more complicated operations (e.g. potential lookup of [[Prototype]]). Thus calling an operation. Test: JSTests/stress/array-shift-intrinsic.js * JSTests/stress/array-shift-intrinsic.js: Added. (shouldBe): (shiftOnce): (drain): (shiftIsolated): * Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h: (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects): * Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp: (JSC::DFG::ByteCodeParser::handleIntrinsicCall): * Source/JavaScriptCore/dfg/DFGClobberize.h: (JSC::DFG::clobberize): * Source/JavaScriptCore/dfg/DFGCloneHelper.h: * Source/JavaScriptCore/dfg/DFGDoesGC.cpp: (JSC::DFG::doesGC): * Source/JavaScriptCore/dfg/DFGFixupPhase.cpp: (JSC::DFG::FixupPhase::fixupNode): * Source/JavaScriptCore/dfg/DFGNode.h: (JSC::DFG::Node::hasHeapPrediction): (JSC::DFG::Node::hasStorageChild const): (JSC::DFG::Node::storageChildIndex): (JSC::DFG::Node::hasArrayMode): * Source/JavaScriptCore/dfg/DFGNodeType.h: * Source/JavaScriptCore/dfg/DFGOperations.cpp: (JSC::DFG::JSC_DEFINE_JIT_OPERATION): * Source/JavaScriptCore/dfg/DFGOperations.h: * Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp: * Source/JavaScriptCore/dfg/DFGSafeToExecute.h: (JSC::DFG::safeToExecute): * Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp: (JSC::DFG::SpeculativeJIT::compile): * Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp: (JSC::DFG::SpeculativeJIT::compile): * Source/JavaScriptCore/ftl/FTLCapabilities.cpp: (JSC::FTL::canCompile): * Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp: (JSC::FTL::DFG::LowerDFGToB3::compileNode): (JSC::FTL::DFG::LowerDFGToB3::compileArrayShift): * Source/JavaScriptCore/runtime/ArrayPrototype.cpp: (JSC::ArrayPrototype::finishCreation): * Source/JavaScriptCore/runtime/Intrinsic.h: Canonical link: https://commits.webkit.org/313453@main
1 parent 6c76663 commit dfe5dc6

19 files changed

Lines changed: 399 additions & 10 deletions
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
function shouldBe(actual, expected) {
2+
if (actual !== expected)
3+
throw new Error("Bad value: " + actual + ", expected: " + expected);
4+
}
5+
6+
function shiftOnce(array) {
7+
return array.shift();
8+
}
9+
noInline(shiftOnce);
10+
11+
function drain(array) {
12+
var results = [];
13+
while (array.length)
14+
results.push(array.shift());
15+
return results;
16+
}
17+
noInline(drain);
18+
19+
// Length-0: fast path returns undefined.
20+
(function () {
21+
for (var i = 0; i < testLoopCount; ++i) {
22+
var array = [];
23+
shouldBe(shiftOnce(array), undefined);
24+
shouldBe(array.length, 0);
25+
}
26+
})();
27+
28+
// Length-1 Int32 fast path.
29+
(function () {
30+
for (var i = 0; i < testLoopCount; ++i) {
31+
var array = [42];
32+
shouldBe(shiftOnce(array), 42);
33+
shouldBe(array.length, 0);
34+
}
35+
})();
36+
37+
// Length-1 Double fast path.
38+
(function () {
39+
for (var i = 0; i < testLoopCount; ++i) {
40+
var array = [3.5];
41+
shouldBe(shiftOnce(array), 3.5);
42+
shouldBe(array.length, 0);
43+
}
44+
})();
45+
46+
// Length-1 Contiguous fast path.
47+
(function () {
48+
for (var i = 0; i < testLoopCount; ++i) {
49+
var array = ["only"];
50+
shouldBe(shiftOnce(array), "only");
51+
shouldBe(array.length, 0);
52+
}
53+
})();
54+
55+
// Multi-element Int32: drains via slow path (operationArrayShift).
56+
(function () {
57+
for (var i = 0; i < testLoopCount; ++i) {
58+
var results = drain([1, 2, 3, 4, 5]);
59+
shouldBe(results.length, 5);
60+
shouldBe(results[0], 1);
61+
shouldBe(results[1], 2);
62+
shouldBe(results[2], 3);
63+
shouldBe(results[3], 4);
64+
shouldBe(results[4], 5);
65+
}
66+
})();
67+
68+
// Multi-element Double: drains via slow path.
69+
(function () {
70+
for (var i = 0; i < testLoopCount; ++i) {
71+
var results = drain([1.5, 2.5, 3.5, 4.5]);
72+
shouldBe(results.length, 4);
73+
shouldBe(results[0], 1.5);
74+
shouldBe(results[1], 2.5);
75+
shouldBe(results[2], 3.5);
76+
shouldBe(results[3], 4.5);
77+
}
78+
})();
79+
80+
// Multi-element Contiguous: drains via slow path.
81+
(function () {
82+
for (var i = 0; i < testLoopCount; ++i) {
83+
var results = drain(["a", "b", "c"]);
84+
shouldBe(results.length, 3);
85+
shouldBe(results[0], "a");
86+
shouldBe(results[1], "b");
87+
shouldBe(results[2], "c");
88+
}
89+
})();
90+
91+
// Length-1 Int32 hole: storage[0] is empty, fast path must take slow case.
92+
(function () {
93+
for (var i = 0; i < testLoopCount; ++i) {
94+
var array = [1];
95+
delete array[0];
96+
shouldBe(array.length, 1);
97+
shouldBe(shiftOnce(array), undefined);
98+
shouldBe(array.length, 0);
99+
}
100+
})();
101+
102+
// Length-1 Double hole: storage[0] is NaN-bit-pattern, fast path must take slow case.
103+
(function () {
104+
for (var i = 0; i < testLoopCount; ++i) {
105+
var array = [1.5];
106+
delete array[0];
107+
shouldBe(array.length, 1);
108+
shouldBe(shiftOnce(array), undefined);
109+
shouldBe(array.length, 0);
110+
}
111+
})();
112+
113+
// Length-1 Double containing real NaN: fast path branchIfNaN takes slow case but result must be NaN.
114+
(function () {
115+
for (var i = 0; i < testLoopCount; ++i) {
116+
var array = [NaN];
117+
var result = shiftOnce(array);
118+
shouldBe(result !== result, true);
119+
shouldBe(array.length, 0);
120+
}
121+
})();
122+
123+
// Repeated shift on the same array exercises arrayMode transitions and slow path.
124+
(function () {
125+
for (var i = 0; i < testLoopCount; ++i) {
126+
var array = [10, 20, 30];
127+
shouldBe(shiftOnce(array), 10);
128+
shouldBe(shiftOnce(array), 20);
129+
shouldBe(shiftOnce(array), 30);
130+
shouldBe(shiftOnce(array), undefined);
131+
shouldBe(array.length, 0);
132+
}
133+
})();
134+
135+
// With Array.prototype[0] set, a length-1 hole array must shift to the
136+
// prototype value. The intrinsic's slow path (operationArrayShift) reads
137+
// through the prototype chain via getIndex.
138+
(function () {
139+
function shiftIsolated(array) {
140+
return array.shift();
141+
}
142+
noInline(shiftIsolated);
143+
144+
for (var i = 0; i < testLoopCount; ++i)
145+
shiftIsolated([1]);
146+
147+
Array.prototype[0] = "proto-zero";
148+
try {
149+
var array = [42];
150+
delete array[0];
151+
shouldBe(shiftIsolated(array), "proto-zero");
152+
shouldBe(array.length, 0);
153+
} finally {
154+
delete Array.prototype[0];
155+
}
156+
})();

‎Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3344,6 +3344,11 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
33443344
clobberWorld();
33453345
makeHeapTopForNode(node);
33463346
break;
3347+
3348+
case ArrayShift:
3349+
clobberWorld();
3350+
makeHeapTopForNode(node);
3351+
break;
33473352

33483353
case GetMyArgumentByVal:
33493354
case GetMyArgumentByValOutOfBounds: {

‎Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2967,7 +2967,29 @@ auto ByteCodeParser::handleIntrinsicCall(Node* callee, Operand resultOperand, Ca
29672967
setResult(arrayPop);
29682968
return CallOptimizationResult::Inlined;
29692969
}
2970-
2970+
2971+
default:
2972+
return CallOptimizationResult::DidNothing;
2973+
}
2974+
}
2975+
2976+
case ArrayShiftIntrinsic: {
2977+
if (!is64Bit())
2978+
return CallOptimizationResult::DidNothing;
2979+
2980+
ArrayMode arrayMode = getArrayMode(Array::Write);
2981+
if (!arrayMode.isJSArray())
2982+
return CallOptimizationResult::DidNothing;
2983+
switch (arrayMode.type()) {
2984+
case Array::Int32:
2985+
case Array::Double:
2986+
case Array::Contiguous: {
2987+
insertChecks();
2988+
Node* arrayShift = addToGraph(ArrayShift, OpInfo(arrayMode.asWord()), OpInfo(prediction), get(virtualRegisterForArgumentIncludingThis(0, registerOffset)));
2989+
setResult(arrayShift);
2990+
return CallOptimizationResult::Inlined;
2991+
}
2992+
29712993
default:
29722994
return CallOptimizationResult::DidNothing;
29732995
}

‎Source/JavaScriptCore/dfg/DFGClobberize.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
171171
case ArrayifyToStructure:
172172
case ArrayPush:
173173
case ArrayPop:
174+
case ArrayShift:
174175
case ArrayIncludes:
175176
case ArrayIndexOf:
176177
case HasIndexedProperty:
@@ -794,6 +795,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
794795
case DeleteByVal:
795796
case ArrayPush:
796797
case ArrayPop:
798+
case ArrayShift:
797799
case ArraySplice:
798800
case Call:
799801
case DirectCall:

‎Source/JavaScriptCore/dfg/DFGCloneHelper.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ BasicBlock* CloneHelper::cloneBlock(BasicBlock* const block, const CustomizeSucc
162162
CLONE_STATUS(ArrayIndexOf, Common) \
163163
CLONE_STATUS(ArrayPop, Common) \
164164
CLONE_STATUS(ArrayPush, Common) \
165+
CLONE_STATUS(ArrayShift, Common) \
165166
CLONE_STATUS(ArraySlice, Common) \
166167
CLONE_STATUS(ArrayConcatArray, Common) \
167168
CLONE_STATUS(ArrayConcatAppendOne, Common) \

‎Source/JavaScriptCore/dfg/DFGDoesGC.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ bool doesGC(Graph& graph, Node* node)
289289
#if ASSERT_ENABLED
290290
case ArrayPush:
291291
case ArrayPop:
292+
case ArrayShift:
292293
case ArraySplice:
293294
case PushWithScope:
294295
case CreateActivation:

‎Source/JavaScriptCore/dfg/DFGFixupPhase.cpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,6 +1774,12 @@ class FixupPhase : public Phase {
17741774
break;
17751775
}
17761776

1777+
case ArrayShift: {
1778+
blessArrayOperation(node->child1(), Edge(), node->child2());
1779+
fixEdge<KnownCellUse>(node->child1());
1780+
break;
1781+
}
1782+
17771783
case ArraySlice: {
17781784
fixEdge<KnownCellUse>(m_graph.varArgChild(node, 0));
17791785
if (node->numChildren() >= 3) {

‎Source/JavaScriptCore/dfg/DFGNode.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,6 +2126,7 @@ struct Node {
21262126
case GetArgument:
21272127
case ArrayPop:
21282128
case ArrayPush:
2129+
case ArrayShift:
21292130
case ArraySplice:
21302131
case RegExpExec:
21312132
case RegExpExecNonGlobalOrSticky:
@@ -2318,6 +2319,7 @@ struct Node {
23182319
case AtomicsXor:
23192320
case ArrayPush:
23202321
case ArrayPop:
2322+
case ArrayShift:
23212323
case GetArrayLength:
23222324
case GetUndetachedTypeArrayLength:
23232325
case GetTypedArrayLengthAsInt52:
@@ -2360,6 +2362,7 @@ struct Node {
23602362
return 0;
23612363

23622364
case ArrayPop:
2365+
case ArrayShift:
23632366
case GetArrayLength:
23642367
case GetUndetachedTypeArrayLength:
23652368
case GetTypedArrayLengthAsInt52:
@@ -2732,6 +2735,7 @@ struct Node {
27322735
case ArrayifyToStructure:
27332736
case ArrayPush:
27342737
case ArrayPop:
2738+
case ArrayShift:
27352739
case ArrayIncludes:
27362740
case ArrayIndexOf:
27372741
case HasIndexedProperty:

‎Source/JavaScriptCore/dfg/DFGNodeType.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ namespace JSC { namespace DFG {
344344
/* Optimizations for array mutation. */\
345345
macro(ArrayPush, NodeResultJS | NodeMustGenerate | NodeHasVarArgs) \
346346
macro(ArrayPop, NodeResultJS | NodeMustGenerate) \
347+
macro(ArrayShift, NodeResultJS | NodeMustGenerate) \
347348
macro(ArraySlice, NodeResultJS | NodeMustGenerate | NodeHasVarArgs) \
348349
macro(ArrayConcatArray, NodeResultJS | NodeMustGenerate) \
349350
macro(ArrayConcatAppendOne, NodeResultJS | NodeMustGenerate) \

‎Source/JavaScriptCore/dfg/DFGOperations.cpp‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,12 +1247,41 @@ JSC_DEFINE_JIT_OPERATION(operationArrayPopAndRecoverLength, EncodedJSValue, (JSG
12471247
CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
12481248
JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
12491249
auto scope = DECLARE_THROW_SCOPE(vm);
1250-
1250+
12511251
array->butterfly()->setPublicLength(array->butterfly()->publicLength() + 1);
1252-
1252+
12531253
OPERATION_RETURN(scope, JSValue::encode(array->pop(globalObject)));
12541254
}
12551255

1256+
JSC_DEFINE_JIT_OPERATION(operationArrayShift, EncodedJSValue, (JSGlobalObject* globalObject, JSArray* array))
1257+
{
1258+
VM& vm = globalObject->vm();
1259+
CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
1260+
JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
1261+
auto scope = DECLARE_THROW_SCOPE(vm);
1262+
1263+
JSValue result = array->fastShift(vm);
1264+
if (result)
1265+
OPERATION_RETURN(scope, JSValue::encode(result));
1266+
1267+
uint64_t length = toLength(globalObject, array);
1268+
OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue());
1269+
1270+
if (!length) {
1271+
scope.release();
1272+
setLength(globalObject, vm, array, length);
1273+
OPERATION_RETURN(scope, JSValue::encode(jsUndefined()));
1274+
}
1275+
1276+
JSValue front = array->getIndex(globalObject, 0);
1277+
OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue());
1278+
shift<JSArray::ShiftCountForShift>(globalObject, array, 0, 1, 0, length);
1279+
OPERATION_RETURN_IF_EXCEPTION(scope, encodedJSValue());
1280+
scope.release();
1281+
setLength(globalObject, vm, array, length - 1);
1282+
OPERATION_RETURN(scope, JSValue::encode(front));
1283+
}
1284+
12561285
template<bool ignoreResult>
12571286
static ALWAYS_INLINE EncodedJSValue arraySpliceImpl(JSGlobalObject* globalObject, JSArray* base, int32_t start, int32_t deleteCount, EncodedJSValue* buffer, unsigned itemCount)
12581287
{

0 commit comments

Comments
 (0)