Add a slice intrinsic to the DFG/FTL
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Jan 2017 07:53:07 +0000 (07:53 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 7 Jan 2017 07:53:07 +0000 (07:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=166707

Reviewed by Filip Pizlo.

JSTests:

* stress/array-slice-intrinsic.js: Added.
(assert):
(shallowEq):
(runTest1):
(runTest2):
* stress/array-slice-jettison-on-constructor-change.js: Added.
(assert):
(runTest1):
(runTest2):
(addRandomProperties):
(runTests):
* stress/array-slice-osr-exit-2.js: Added.
(assert):
(Foo):
(shallowEq):
(runTest1):
(runTest2):
(addRandomProperties):
(runTests):
* stress/array-slice-osr-exit.js: Added.
(assert):
(Foo):
(shallowEq):
(runTest1):
(runTest2):
(addRandomProperties):
(runTests):

Source/JavaScriptCore:

The gist of this patch is to inline Array.prototype.slice
into the DFG/FTL. The implementation in the DFG-backend
and FTLLowerDFGToB3 is just a straight forward implementation
of what the C function is doing. The more interesting bits
of this patch are setting up the proper watchpoints and conditions
in the executing code to prove that its safe to skip all of the
observable JS actions that Array.prototype.slice normally does.

We perform the following proofs:
1. Array.prototype.constructor has not changed (via a watchpoint).
2. That Array.prototype.constructor[Symbol.species] has not changed (via a watchpoint).
3. The global object is not having a bad time.
3. The array that is being sliced has an original array structure.
5. Array.prototype/Object.prototype have not transitioned.

Conditions 1, 2, and 3 are strictly required.

4 is ensuring a couple things:
1. That a "constructor" property hasn't been added to the array
we're slicing since we're supposed to perform a Get(array, "constructor").
2. That we're not slicing an instance of a subclass of Array.

We could relax 4.1 in the future if we find other ways to test if
the incoming array hasn't changed the "constructor" property.

I'm seeing a 5% speedup on crypto-pbkdf2 and often a 1% speedup on
the total benchmark (the results are sometimes noisy).

* bytecode/ExitKind.cpp:
(JSC::exitKindToString):
* bytecode/ExitKind.h:
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsicCall):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNode.h:
(JSC::DFG::Node::hasHeapPrediction):
(JSC::DFG::Node::hasArrayMode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileArraySlice):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileArraySlice):
* jit/AssemblyHelpers.cpp:
(JSC::AssemblyHelpers::emitLoadStructure):
* runtime/ArrayPrototype.cpp:
(JSC::ArrayPrototype::finishCreation):
(JSC::speciesWatchpointIsValid):
(JSC::speciesConstructArray):
(JSC::arrayProtoFuncSlice):
(JSC::arrayProtoPrivateFuncConcatMemcpy):
(JSC::ArrayPrototype::initializeSpeciesWatchpoint):
(JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire):
(JSC::speciesWatchpointsValid): Deleted.
(JSC::ArrayPrototype::attemptToInitializeSpeciesWatchpoint): Deleted.
* runtime/ArrayPrototype.h:
(JSC::ArrayPrototype::speciesWatchpointStatus): Deleted.
(): Deleted.
* runtime/Intrinsic.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::JSGlobalObject):
(JSC::JSGlobalObject::init):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::arraySpeciesWatchpoint):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@210476 268f45cc-cd09-0410-ab3c-d52691b4dbfc

27 files changed:
JSTests/ChangeLog
JSTests/stress/array-slice-intrinsic.js [new file with mode: 0644]
JSTests/stress/array-slice-jettison-on-constructor-change.js [new file with mode: 0644]
JSTests/stress/array-slice-osr-exit-2.js [new file with mode: 0644]
JSTests/stress/array-slice-osr-exit.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNode.h
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/AssemblyHelpers.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.h
Source/JavaScriptCore/runtime/Intrinsic.h
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h

index 456dc97..f97f13f 100644 (file)
@@ -1,3 +1,38 @@
+2017-01-06  Saam Barati  <sbarati@apple.com>
+
+        Add a slice intrinsic to the DFG/FTL
+        https://bugs.webkit.org/show_bug.cgi?id=166707
+
+        Reviewed by Filip Pizlo.
+
+        * stress/array-slice-intrinsic.js: Added.
+        (assert):
+        (shallowEq):
+        (runTest1):
+        (runTest2):
+        * stress/array-slice-jettison-on-constructor-change.js: Added.
+        (assert):
+        (runTest1):
+        (runTest2):
+        (addRandomProperties):
+        (runTests):
+        * stress/array-slice-osr-exit-2.js: Added.
+        (assert):
+        (Foo):
+        (shallowEq):
+        (runTest1):
+        (runTest2):
+        (addRandomProperties):
+        (runTests):
+        * stress/array-slice-osr-exit.js: Added.
+        (assert):
+        (Foo):
+        (shallowEq):
+        (runTest1):
+        (runTest2):
+        (addRandomProperties):
+        (runTests):
+
 2017-01-06  Michael Saboff  <msaboff@apple.com>
 
         @putByValDirect in Array.of and Array.from overwrites non-writable/configurable properties
diff --git a/JSTests/stress/array-slice-intrinsic.js b/JSTests/stress/array-slice-intrinsic.js
new file mode 100644 (file)
index 0000000..725a74d
--- /dev/null
@@ -0,0 +1,49 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+function shallowEq(a, b) {
+    assert(a.length === b.length);
+    for (let i = 0; i < a.length; i++)
+        assert(a[i] === b[i]);
+}
+noInline(shallowEq);
+
+let tests = [
+    [[1,2,3,4,5], [1,2,3,4,5], 0, 5],
+    [[1,2,3,4,5], [1,2,3,4,5], 0],
+    [[1,2,3,4,5], [4], -2, -1],
+    [[1,2,3,4,5], [5], -1],
+    [[1,2,3,4,5], [5], -1, 5],
+    [[1,2,3,4,5], [], -10, -20],
+    [[1,2,3,4,5], [], -20, -10],
+    [[1,2,3,4,5], [], 6, 4],
+    [[1,2,3,4,5], [], 3, 2],
+    [[1,2,3,4,5], [4,5], 3, 10],
+    [[1,2,3,4,5], [3,4,5], 2, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -10, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -5, 10],
+    [[1,2,3,4,5], [2,3,4,5], -4, 10],
+];
+
+function runTest1(a, b) {
+    return a.slice(b);
+}
+noInline(runTest1);
+
+function runTest2(a, b, c) {
+    return a.slice(b, c);
+}
+noInline(runTest2);
+
+for (let i = 0; i < 10000; i++) {
+    for (let [input, output, ...args] of tests) {
+        assert(args.length === 1 || args.length === 2);
+        if (args.length === 1)
+            shallowEq(runTest1(input, args[0]), output);
+        else
+            shallowEq(runTest2(input, args[0], args[1]), output);
+    }
+}
diff --git a/JSTests/stress/array-slice-jettison-on-constructor-change.js b/JSTests/stress/array-slice-jettison-on-constructor-change.js
new file mode 100644 (file)
index 0000000..4049da9
--- /dev/null
@@ -0,0 +1,72 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+let shouldBeNewConstructor = false;
+const newConstructor = {};
+
+function shallowEq(a, b) {
+    assert(a.length === b.length);
+    if (shouldBeNewConstructor)
+        assert(b.constructor === newConstructor);
+    for (let i = 0; i < a.length; i++)
+        assert(a[i] === b[i]);
+}
+noInline(shallowEq);
+
+let tests = [
+    [[1,2,3,4,5], [1,2,3,4,5], 0, 5],
+    [[1,2,3,4,5], [1,2,3,4,5], 0],
+    [[1,2,3,4,5], [4], -2, -1],
+    [[1,2,3,4,5], [5], -1],
+    [[1,2,3,4,5], [5], -1, 5],
+    [[1,2,3,4,5], [], -10, -20],
+    [[1,2,3,4,5], [], -20, -10],
+    [[1,2,3,4,5], [], 6, 4],
+    [[1,2,3,4,5], [], 3, 2],
+    [[1,2,3,4,5], [4,5], 3, 10],
+    [[1,2,3,4,5], [3,4,5], 2, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -10, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -5, 10],
+    [[1,2,3,4,5], [2,3,4,5], -4, 10],
+];
+
+function runTest1(a, b) {
+    let result = a.slice(b);
+    return result;
+}
+noInline(runTest1);
+
+function runTest2(a, b, c) {
+    let result = a.slice(b, c);
+    return result;
+}
+noInline(runTest2);
+
+function addRandomProperties(input) {
+    for (let i = 0; i < 4; i++) {
+        input["prop" + i + ((Math.random() * 100000) | 0)] = i;
+    }
+}
+noInline(addRandomProperties);
+
+function runTests() {
+    for (let i = 0; i < 10000; i++) {
+        for (let [input, output, ...args] of tests) {
+            addRandomProperties(input);
+            assert(args.length === 1 || args.length === 2);
+            if (args.length === 1)
+                shallowEq(runTest1(input, args[0]), output);
+            else
+                shallowEq(runTest2(input, args[0], args[1]), output);
+        }
+    }
+}
+
+runTests();
+
+Array.prototype.constructor = newConstructor;
+shouldBeNewConstructor = true;
+runTests();
diff --git a/JSTests/stress/array-slice-osr-exit-2.js b/JSTests/stress/array-slice-osr-exit-2.js
new file mode 100644 (file)
index 0000000..2db07ce
--- /dev/null
@@ -0,0 +1,76 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+class Foo extends Array {
+    constructor(...args) {
+        super(...args);
+    }
+};
+function shallowEq(a, b) {
+    assert(a.length === b.length);
+    for (let i = 0; i < a.length; i++)
+        assert(a[i] === b[i]);
+}
+noInline(shallowEq);
+
+let tests = [
+    [[1,2,3,4,5], [1,2,3,4,5], 0, 5],
+    [[1,2,3,4,5], [1,2,3,4,5], 0],
+    [[1,2,3,4,5], [4], -2, -1],
+    [[1,2,3,4,5], [5], -1],
+    [[1,2,3,4,5], [5], -1, 5],
+    [[1,2,3,4,5], [], -10, -20],
+    [[1,2,3,4,5], [], -20, -10],
+    [[1,2,3,4,5], [], 6, 4],
+    [[1,2,3,4,5], [], 3, 2],
+    [[1,2,3,4,5], [4,5], 3, 10],
+    [[1,2,3,4,5], [3,4,5], 2, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -10, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -5, 10],
+    [[1,2,3,4,5], [2,3,4,5], -4, 10],
+];
+
+function runTest1(a, b) {
+    let result = a.slice(b);
+    assert(a instanceof Foo === result instanceof Foo);
+    return result;
+}
+noInline(runTest1);
+
+function runTest2(a, b, c) {
+    let result = a.slice(b, c);
+    assert(a instanceof Foo === result instanceof Foo);
+    return result;
+}
+noInline(runTest2);
+
+function addRandomProperties(input) {
+    for (let i = 0; i < 4; i++) {
+        input["prop" + i + ((Math.random() * 100000) | 0)] = i;
+    }
+}
+noInline(addRandomProperties);
+
+function runTests() {
+    for (let i = 0; i < 10000; i++) {
+        for (let [input, output, ...args] of tests) {
+            addRandomProperties(input);
+            assert(args.length === 1 || args.length === 2);
+            if (args.length === 1)
+                shallowEq(runTest1(input, args[0]), output);
+            else
+                shallowEq(runTest2(input, args[0], args[1]), output);
+        }
+    }
+}
+
+runTests();
+
+tests.push([new Foo(1,2,3,4,5), [1,2,3,4,5], -10, 10]);
+tests.push([new Foo(1,2,3,4,5), [1,2,3,4,5], -5, 10]);
+tests.push([new Foo(1,2,3,4,5), [2,3,4,5], -4, 10]);
+tests.push([new Foo(1,2,3,4,5), [2,3,4,5], -4]);
+runTests();
diff --git a/JSTests/stress/array-slice-osr-exit.js b/JSTests/stress/array-slice-osr-exit.js
new file mode 100644 (file)
index 0000000..93ed2bb
--- /dev/null
@@ -0,0 +1,74 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad")
+}
+noInline(assert);
+
+class Foo extends Array {
+    constructor(...args) {
+        super(...args);
+    }
+};
+function shallowEq(a, b) {
+    assert(a.length === b.length);
+    for (let i = 0; i < a.length; i++)
+        assert(a[i] === b[i]);
+}
+noInline(shallowEq);
+
+let tests = [
+    [[1,2,3,4,5], [1,2,3,4,5], 0, 5],
+    [[1,2,3,4,5], [1,2,3,4,5], 0],
+    [[1,2,3,4,5], [4], -2, -1],
+    [[1,2,3,4,5], [5], -1],
+    [[1,2,3,4,5], [5], -1, 5],
+    [[1,2,3,4,5], [], -10, -20],
+    [[1,2,3,4,5], [], -20, -10],
+    [[1,2,3,4,5], [], 6, 4],
+    [[1,2,3,4,5], [], 3, 2],
+    [[1,2,3,4,5], [4,5], 3, 10],
+    [[1,2,3,4,5], [3,4,5], 2, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -10, 10],
+    [[1,2,3,4,5], [1,2,3,4,5], -5, 10],
+    [[1,2,3,4,5], [2,3,4,5], -4, 10],
+];
+tests.push([new Foo(1,2,3,4,5), [1,2,3,4,5], -10, 10]);
+tests.push([new Foo(1,2,3,4,5), [1,2,3,4,5], -5, 10]);
+tests.push([new Foo(1,2,3,4,5), [2,3,4,5], -4, 10]);
+tests.push([new Foo(1,2,3,4,5), [2,3,4,5], -4]);
+
+function runTest1(a, b) {
+    let result = a.slice(b);
+    assert(a instanceof Foo === result instanceof Foo);
+    return result;
+}
+noInline(runTest1);
+
+function runTest2(a, b, c) {
+    let result = a.slice(b, c);
+    assert(a instanceof Foo === result instanceof Foo);
+    return result;
+}
+noInline(runTest2);
+
+function addRandomProperties(input) {
+    for (let i = 0; i < 4; i++) {
+        input["prop" + i + ((Math.random() * 100000) | 0)] = i;
+    }
+}
+noInline(addRandomProperties);
+
+function runTests() {
+    for (let i = 0; i < 10000; i++) {
+        for (let [input, output, ...args] of tests) {
+            addRandomProperties(input);
+            assert(args.length === 1 || args.length === 2);
+            if (args.length === 1)
+                shallowEq(runTest1(input, args[0]), output);
+            else
+                shallowEq(runTest2(input, args[0], args[1]), output);
+        }
+    }
+}
+
+runTests();
index b16f923..67bd125 100644 (file)
@@ -1,3 +1,92 @@
+2017-01-06  Saam Barati  <sbarati@apple.com>
+
+        Add a slice intrinsic to the DFG/FTL
+        https://bugs.webkit.org/show_bug.cgi?id=166707
+
+        Reviewed by Filip Pizlo.
+
+        The gist of this patch is to inline Array.prototype.slice
+        into the DFG/FTL. The implementation in the DFG-backend
+        and FTLLowerDFGToB3 is just a straight forward implementation
+        of what the C function is doing. The more interesting bits
+        of this patch are setting up the proper watchpoints and conditions
+        in the executing code to prove that its safe to skip all of the
+        observable JS actions that Array.prototype.slice normally does.
+        
+        We perform the following proofs:
+        1. Array.prototype.constructor has not changed (via a watchpoint).
+        2. That Array.prototype.constructor[Symbol.species] has not changed (via a watchpoint).
+        3. The global object is not having a bad time.
+        3. The array that is being sliced has an original array structure.
+        5. Array.prototype/Object.prototype have not transitioned.
+        
+        Conditions 1, 2, and 3 are strictly required.
+        
+        4 is ensuring a couple things:
+        1. That a "constructor" property hasn't been added to the array
+        we're slicing since we're supposed to perform a Get(array, "constructor").
+        2. That we're not slicing an instance of a subclass of Array.
+        
+        We could relax 4.1 in the future if we find other ways to test if
+        the incoming array hasn't changed the "constructor" property.
+        
+        I'm seeing a 5% speedup on crypto-pbkdf2 and often a 1% speedup on
+        the total benchmark (the results are sometimes noisy).
+
+        * bytecode/ExitKind.cpp:
+        (JSC::exitKindToString):
+        * bytecode/ExitKind.h:
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNode.h:
+        (JSC::DFG::Node::hasHeapPrediction):
+        (JSC::DFG::Node::hasArrayMode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileArraySlice):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileArraySlice):
+        * jit/AssemblyHelpers.cpp:
+        (JSC::AssemblyHelpers::emitLoadStructure):
+        * runtime/ArrayPrototype.cpp:
+        (JSC::ArrayPrototype::finishCreation):
+        (JSC::speciesWatchpointIsValid):
+        (JSC::speciesConstructArray):
+        (JSC::arrayProtoFuncSlice):
+        (JSC::arrayProtoPrivateFuncConcatMemcpy):
+        (JSC::ArrayPrototype::initializeSpeciesWatchpoint):
+        (JSC::ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire):
+        (JSC::speciesWatchpointsValid): Deleted.
+        (JSC::ArrayPrototype::attemptToInitializeSpeciesWatchpoint): Deleted.
+        * runtime/ArrayPrototype.h:
+        (JSC::ArrayPrototype::speciesWatchpointStatus): Deleted.
+        (): Deleted.
+        * runtime/Intrinsic.h:
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::JSGlobalObject):
+        (JSC::JSGlobalObject::init):
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::arraySpeciesWatchpoint):
+
 2017-01-06  Mark Lam  <mark.lam@apple.com>
 
         The ObjC API's JSVirtualMachine's map tables need to be guarded by a lock.
index 2791aea..a9021f5 100644 (file)
@@ -1650,6 +1650,25 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         clobberWorld(node->origin.semantic, clobberLimit);
         forNode(node).setType(SpecBytecodeNumber);
         break;
+
+    case ArraySlice:
+        IndexingType indexingType;
+        switch (node->arrayMode().type()) {
+        case Array::Double:
+            indexingType = ArrayWithDouble;
+            break;
+        case Array::Int32:
+            indexingType = ArrayWithInt32;
+            break;
+        case Array::Contiguous:
+            indexingType = ArrayWithContiguous;
+            break;
+        default:
+            DFG_CRASH(m_graph, node, "Bad array mode.");
+        }
+
+        forNode(node).set(m_graph, m_graph.globalObjectFor(node->origin.semantic)->arrayStructureForIndexingTypeDuringAllocation(indexingType));
+        break;
             
     case ArrayPop:
         clobberWorld(node->origin.semantic, clobberLimit);
index d452b92..789337c 100644 (file)
@@ -2246,6 +2246,92 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
             return false;
         }
     }
+
+    case ArraySliceIntrinsic: {
+#if USE(JSVALUE32_64)
+        if (isX86()) {
+            // There aren't enough registers for this to be done easily.
+            return false;
+        }
+#endif
+        if (argumentCountIncludingThis < 2)
+            return false;
+
+        if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadIndexingType)
+            || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadConstantCache)
+            || m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadCache))
+            return false;
+
+        ArrayMode arrayMode = getArrayMode(m_currentInstruction[OPCODE_LENGTH(op_call) - 2].u.arrayProfile);
+        if (!arrayMode.isJSArray())
+            return false;
+
+        if (arrayMode.arrayClass() != Array::OriginalArray)
+            return false;
+
+        switch (arrayMode.type()) {
+        case Array::Double:
+        case Array::Int32:
+        case Array::Contiguous: {
+            JSGlobalObject* globalObject = m_graph.globalObjectFor(currentNodeOrigin().semantic);
+
+            InlineWatchpointSet& objectPrototypeTransition = globalObject->objectPrototype()->structure()->transitionWatchpointSet();
+            InlineWatchpointSet& arrayPrototypeTransition = globalObject->arrayPrototype()->structure()->transitionWatchpointSet();
+
+            // FIXME: We could easily relax the Array/Object.prototype transition as long as we OSR exitted if we saw a hole.
+            if (globalObject->arraySpeciesWatchpoint().isStillValid()
+                && globalObject->havingABadTimeWatchpoint()->isStillValid()
+                && arrayPrototypeTransition.isStillValid()
+                && objectPrototypeTransition.isStillValid()
+                && globalObject->arrayPrototypeChainIsSane()) {
+
+                m_graph.watchpoints().addLazily(globalObject->arraySpeciesWatchpoint());
+                m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
+                m_graph.watchpoints().addLazily(arrayPrototypeTransition);
+                m_graph.watchpoints().addLazily(objectPrototypeTransition);
+
+                insertChecks();
+
+                Node* array = get(virtualRegisterForArgument(0, registerOffset));
+                // We do a few things here to prove that we aren't skipping doing side-effects in an observable way:
+                // 1. We ensure that the "constructor" property hasn't been changed (because the observable
+                // effects of slice require that we perform a Get(array, "constructor") and we can skip
+                // that if we're an original array structure.
+                //
+                // 2. We check that the array we're calling slice on has the same global object as the lexical
+                // global object that this code is running in. This requirement is necessary because we setup the
+                // watchpoints above on the lexical global object. This means that code that calls slice on
+                // arrays produced by other global objects won't get this optimization. We could relax this
+                // requirement in the future by checking that the watchpoint hasn't fired at runtime in the code
+                // we generate instead of registering it as a watchpoint that would invalidate the compilation.
+                //
+                // 3. By proving we're an original array structure, we guarantee that the incoming array
+                // isn't a subclass of Array.
+
+                Structure* structure = arrayMode.originalArrayStructure(m_graph, currentNodeOrigin().semantic);
+                RELEASE_ASSERT(structure);
+                addToGraph(CheckStructure, OpInfo(m_graph.addStructureSet(structure)), array);
+
+                addVarArgChild(array);
+                addVarArgChild(get(virtualRegisterForArgument(1, registerOffset))); // Start index.
+                if (argumentCountIncludingThis >= 3)
+                    addVarArgChild(get(virtualRegisterForArgument(2, registerOffset))); // End index.
+                addVarArgChild(addToGraph(GetButterfly, array));
+
+                Node* arraySlice = addToGraph(Node::VarArg, ArraySlice, OpInfo(arrayMode.asWord()), OpInfo(prediction));
+                set(VirtualRegister(resultOperand), arraySlice);
+                return true;
+            }
+
+            return false;
+        }
+        default:
+            return false;
+        }
+
+        RELEASE_ASSERT_NOT_REACHED();
+        return false;
+    }
         
     case ArrayPopIntrinsic: {
         if (argumentCountIncludingThis != 1)
index 0ced522..c04e2ee 100644 (file)
@@ -503,6 +503,29 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         read(MiscFields);
         def(HeapLocation(IsFunctionLoc, MiscFields, node->child1()), LazyNode(node));
         return;
+
+    case ArraySlice:
+        read(MiscFields);
+        read(JSCell_indexingType);
+        read(JSCell_structureID);
+        read(JSObject_butterfly);
+        read(Butterfly_publicLength);
+        switch (node->arrayMode().type()) {
+        case Array::Double:
+            read(IndexedDoubleProperties);
+            break;
+        case Array::Int32:
+            read(IndexedInt32Properties);
+            break;
+        case Array::Contiguous:
+            read(IndexedContiguousProperties);
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+        read(HeapObjectCount);
+        write(HeapObjectCount);
+        return;
         
     case GetById:
     case GetByIdFlush:
index e71f2b3..cdc0406 100644 (file)
@@ -309,6 +309,7 @@ bool doesGC(Graph& graph, Node* node)
     case ToLowerCase:
     case CallDOMGetter:
     case CallDOM:
+    case ArraySlice:
         return true;
         
     case MultiPutByOffset:
index ca562b4..1ad7bc1 100644 (file)
@@ -931,6 +931,14 @@ private:
             fixEdge<KnownCellUse>(node->child1());
             break;
         }
+
+        case ArraySlice: {
+            fixEdge<KnownCellUse>(m_graph.varArgChild(node, 0));
+            fixEdge<Int32Use>(m_graph.varArgChild(node, 1));
+            if (node->numChildren() == 4)
+                fixEdge<Int32Use>(m_graph.varArgChild(node, 2));
+            break;
+        }
             
         case RegExpExec:
         case RegExpTest: {
index b28157f..c636435 100644 (file)
@@ -1472,6 +1472,7 @@ public:
         case GetArgument:
         case ArrayPop:
         case ArrayPush:
+        case ArraySlice:
         case RegExpExec:
         case RegExpTest:
         case GetGlobalVar:
@@ -1796,6 +1797,7 @@ public:
         case Arrayify:
         case ArrayifyToStructure:
         case ArrayPush:
+        case ArraySlice:
         case ArrayPop:
         case HasIndexedProperty:
             return true;
index d45e4df..6af4b35 100644 (file)
@@ -248,6 +248,7 @@ namespace JSC { namespace DFG {
     /* Optimizations for array mutation. */\
     macro(ArrayPush, NodeResultJS | NodeMustGenerate) \
     macro(ArrayPop, NodeResultJS | NodeMustGenerate) \
+    macro(ArraySlice, NodeResultJS | NodeMustGenerate | NodeHasVarArgs) \
     \
     /* Optimizations for regular expression matching. */\
     macro(RegExpExec, NodeResultJS | NodeMustGenerate) \
index 356ef3e..3c0cd9e 100644 (file)
@@ -678,6 +678,7 @@ private:
 
         case ArrayPop:
         case ArrayPush:
+        case ArraySlice:
         case RegExpExec:
         case RegExpTest:
         case StringReplace:
index f3f7892..60338fb 100644 (file)
@@ -402,6 +402,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case GetArrayLength:
     case ArrayPush:
     case ArrayPop:
+    case ArraySlice:
     case StringCharAt:
     case StringCharCodeAt:
         return node->arrayMode().alreadyChecked(graph, node, state.forNode(node->child1()));
index 82bbe2b..665330e 100644 (file)
@@ -7171,6 +7171,127 @@ void SpeculativeJIT::compileGetRestLength(Node* node)
     int32Result(resultGPR, node);
 }
 
+void SpeculativeJIT::compileArraySlice(Node* node)
+{
+    ASSERT(node->op() == ArraySlice);
+
+    JSGlobalObject* globalObject = m_jit.graph().globalObjectFor(node->origin.semantic);
+
+    GPRTemporary temp(this);
+    StorageOperand storage(this, node->numChildren() == 3 ? m_jit.graph().varArgChild(node, 2) : m_jit.graph().varArgChild(node, 3));
+    GPRTemporary result(this);
+    
+    GPRReg storageGPR = storage.gpr();
+    GPRReg resultGPR = result.gpr();
+    GPRReg tempGPR = temp.gpr();
+
+    IndexingType indexingType;
+    switch (node->arrayMode().type()) {
+    case Array::Int32:
+        indexingType = ArrayWithInt32;
+        break;
+    case Array::Contiguous:
+        indexingType = ArrayWithContiguous;
+        break;
+    case Array::Double:
+        indexingType = ArrayWithDouble;
+        break;
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+    }
+
+    auto populateIndex = [&] (unsigned childIndex, GPRReg length, GPRReg result) {
+        SpeculateInt32Operand index(this, m_jit.graph().varArgChild(node, childIndex));
+        GPRReg indexGPR = index.gpr();
+        MacroAssembler::JumpList done;
+        auto isPositive = m_jit.branch32(MacroAssembler::GreaterThanOrEqual, indexGPR, TrustedImm32(0));
+        m_jit.move(length, result);
+        done.append(m_jit.branchAdd32(MacroAssembler::PositiveOrZero, indexGPR, result));
+        m_jit.move(TrustedImm32(0), result);
+        done.append(m_jit.jump());
+
+        isPositive.link(&m_jit);
+        m_jit.move(indexGPR, result);
+        done.append(m_jit.branch32(MacroAssembler::BelowOrEqual, result, length));
+        m_jit.move(length, result);
+
+        done.link(&m_jit);
+    };
+
+    {
+        GPRTemporary tempLength(this);
+        GPRReg lengthGPR = tempLength.gpr();
+        m_jit.load32(MacroAssembler::Address(storageGPR, Butterfly::offsetOfPublicLength()), lengthGPR);
+
+        if (node->numChildren() == 4)
+            populateIndex(2, lengthGPR, tempGPR);
+        else
+            m_jit.move(lengthGPR, tempGPR);
+
+        GPRTemporary tempStartIndex(this);
+        GPRReg startGPR = tempStartIndex.gpr();
+        populateIndex(1, lengthGPR, startGPR);
+
+        auto tooBig = m_jit.branch32(MacroAssembler::Above, startGPR, tempGPR);
+        m_jit.sub32(startGPR, tempGPR); // the size of the array we'll make.
+        auto done = m_jit.jump();
+
+        tooBig.link(&m_jit);
+        m_jit.move(TrustedImm32(0), tempGPR);
+        done.link(&m_jit);
+    }
+
+    const bool shouldConvertLargeSizeToArrayStorage = false;
+    compileAllocateNewArrayWithSize(globalObject, resultGPR, tempGPR, indexingType, shouldConvertLargeSizeToArrayStorage);
+
+    GPRTemporary temp3(this);
+    GPRTemporary temp4(this);
+    GPRReg tempValue = temp3.gpr();
+    GPRReg loadIndex = temp4.gpr();
+
+    m_jit.load32(MacroAssembler::Address(storageGPR, Butterfly::offsetOfPublicLength()), tempValue);
+    if (node->numChildren() == 4)
+        populateIndex(2, tempValue, tempGPR);
+    else
+        m_jit.move(tempValue, tempGPR);
+    populateIndex(1, tempValue, loadIndex);
+
+    GPRTemporary temp5(this);
+    GPRReg storeIndex = temp5.gpr();
+    m_jit.move(TrustedImmPtr(0), storeIndex);
+
+    GPRTemporary temp2(this);
+    GPRReg resultButterfly = temp2.gpr();
+
+    m_jit.loadPtr(MacroAssembler::Address(resultGPR, JSObject::butterflyOffset()), resultButterfly);
+    m_jit.zeroExtend32ToPtr(tempGPR, tempGPR);
+    m_jit.zeroExtend32ToPtr(loadIndex, loadIndex);
+    auto done = m_jit.branchPtr(MacroAssembler::AboveOrEqual, loadIndex, tempGPR);
+
+    auto loop = m_jit.label();
+#if USE(JSVALUE64)
+    m_jit.load64(
+        MacroAssembler::BaseIndex(storageGPR, loadIndex, MacroAssembler::TimesEight), tempValue);
+    m_jit.store64(
+        tempValue, MacroAssembler::BaseIndex(resultButterfly, storeIndex, MacroAssembler::TimesEight));
+#else
+    m_jit.load32(
+        MacroAssembler::BaseIndex(storageGPR, loadIndex, MacroAssembler::TimesEight, PayloadOffset), tempValue);
+    m_jit.store32(
+        tempValue, MacroAssembler::BaseIndex(resultButterfly, storeIndex, MacroAssembler::TimesEight, PayloadOffset));
+    m_jit.load32(
+        MacroAssembler::BaseIndex(storageGPR, loadIndex, MacroAssembler::TimesEight, TagOffset), tempValue);
+    m_jit.store32(
+        tempValue, MacroAssembler::BaseIndex(resultButterfly, storeIndex, MacroAssembler::TimesEight, TagOffset));
+#endif // USE(JSVALUE64)
+    m_jit.addPtr(TrustedImm32(1), loadIndex);
+    m_jit.addPtr(TrustedImm32(1), storeIndex);
+    m_jit.branchPtr(MacroAssembler::Below, loadIndex, tempGPR).linkTo(loop, &m_jit);
+
+    done.link(&m_jit);
+    cellResult(resultGPR, node);
+}
+
 void SpeculativeJIT::compileNotifyWrite(Node* node)
 {
     WatchpointSet* set = node->watchpointSet();
index b0f0e08..9200873 100644 (file)
@@ -2680,6 +2680,7 @@ public:
     void compileSpread(Node*);
     void compileNewArrayWithSpread(Node*);
     void compileGetRestLength(Node*);
+    void compileArraySlice(Node*);
     void compileNotifyWrite(Node*);
     bool compileRegExpExec(Node*);
     void compileIsObjectOrNull(Node*);
index 4fa8484..c94000a 100644 (file)
@@ -3550,6 +3550,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ArraySlice: {
+        compileArraySlice(node);
+        break;
+    }
+
     case DFG::Jump: {
         jump(node->targetBlock());
         noResult(node);
index e51499d..a532836 100644 (file)
@@ -3462,6 +3462,11 @@ void SpeculativeJIT::compile(Node* node)
         }
         break;
     }
+
+    case ArraySlice: {
+        compileArraySlice(node);
+        break;
+    }
         
     case ArrayPop: {
         ASSERT(node->arrayMode().isJSArray());
index d42ff4a..4383f12 100644 (file)
@@ -282,6 +282,7 @@ inline CapabilityLevel canCompile(Node* node)
     case CheckDOM:
     case CallDOM:
     case CallDOMGetter:
+    case ArraySlice:
         // These are OK.
         break;
 
index 70ebf86..98ba875 100644 (file)
@@ -706,6 +706,9 @@ private:
         case ArrayPop:
             compileArrayPop();
             break;
+        case ArraySlice:
+            compileArraySlice();
+            break;
         case CreateActivation:
             compileCreateActivation();
             break;
@@ -3860,6 +3863,83 @@ private:
             return;
         }
     }
+
+    void compileArraySlice()
+    {
+        JSGlobalObject* globalObject = m_graph.globalObjectFor(m_node->origin.semantic);
+
+        LValue sourceStorage = lowStorage(m_node->numChildren() == 3 ? m_graph.varArgChild(m_node, 2) : m_graph.varArgChild(m_node, 3));
+        LValue inputLength = m_out.load32(sourceStorage, m_heaps.Butterfly_publicLength);
+
+        LValue endBoundary;
+        if (m_node->numChildren() == 3)
+            endBoundary = m_out.load32(sourceStorage, m_heaps.Butterfly_publicLength);
+        else {
+            endBoundary = lowInt32(m_graph.varArgChild(m_node, 2));
+            endBoundary = m_out.select(m_out.greaterThanOrEqual(endBoundary, m_out.constInt32(0)),
+                m_out.select(m_out.above(endBoundary, inputLength), inputLength, endBoundary),
+                m_out.select(m_out.lessThan(m_out.add(inputLength, endBoundary), m_out.constInt32(0)), m_out.constInt32(0), m_out.add(inputLength, endBoundary)));
+        }
+
+        LValue startIndex = lowInt32(m_graph.varArgChild(m_node, 1));
+        startIndex = m_out.select(m_out.greaterThanOrEqual(startIndex, m_out.constInt32(0)),
+            m_out.select(m_out.above(startIndex, inputLength), inputLength, startIndex),
+            m_out.select(m_out.lessThan(m_out.add(inputLength, startIndex), m_out.constInt32(0)), m_out.constInt32(0), m_out.add(inputLength, startIndex)));
+
+        LValue resultLength = m_out.select(m_out.below(startIndex, endBoundary),
+            m_out.sub(endBoundary, startIndex),
+            m_out.constInt32(0));
+
+        IndexingType indexingType;
+        switch (m_node->arrayMode().type()) {
+        case Array::Int32:
+            indexingType = ArrayWithInt32;
+            break;
+        case Array::Contiguous:
+            indexingType = ArrayWithContiguous;
+            break;
+        case Array::Double:
+            indexingType = ArrayWithDouble;
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+
+        Structure* structure = globalObject->arrayStructureForIndexingTypeDuringAllocation(indexingType);
+        auto arrayResult = allocateJSArray(resultLength, structure, false, false);
+
+        LBasicBlock loop = m_out.newBlock();
+        LBasicBlock continuation = m_out.newBlock();
+
+        resultLength = m_out.zeroExtPtr(resultLength);
+        ValueFromBlock startLoadIndex = m_out.anchor(m_out.zeroExtPtr(startIndex));
+        ValueFromBlock startStoreIndex = m_out.anchor(m_out.constIntPtr(0));
+
+        m_out.branch(
+            m_out.below(m_out.constIntPtr(0), resultLength), unsure(loop), unsure(continuation));
+
+        LBasicBlock lastNext = m_out.appendTo(loop, continuation);
+        LValue storeIndex = m_out.phi(pointerType(), startStoreIndex);
+        LValue loadIndex = m_out.phi(pointerType(), startLoadIndex);
+        IndexedAbstractHeap& heap = m_heaps.forArrayType(m_node->arrayMode().type());
+        if (indexingType == ArrayWithDouble) {
+            LValue value = m_out.loadDouble(m_out.baseIndex(heap, sourceStorage, loadIndex));
+            m_out.storeDouble(value, m_out.baseIndex(heap, arrayResult.butterfly, storeIndex));
+        } else {
+            LValue value = m_out.load64(m_out.baseIndex(heap, sourceStorage, loadIndex));
+            m_out.store64(value, m_out.baseIndex(heap, arrayResult.butterfly, storeIndex));
+        }
+        LValue nextStoreIndex = m_out.add(storeIndex, m_out.constIntPtr(1));
+        m_out.addIncomingToPhi(storeIndex, m_out.anchor(nextStoreIndex));
+        m_out.addIncomingToPhi(loadIndex, m_out.anchor(m_out.add(loadIndex, m_out.constIntPtr(1))));
+        m_out.branch(
+            m_out.below(nextStoreIndex, resultLength), unsure(loop), unsure(continuation));
+
+        m_out.appendTo(continuation, lastNext);
+
+        mutatorFence();
+        setJSValue(arrayResult.array);
+    }
     
     void compileArrayPop()
     {
index 4b4cd8c..ba017e8 100644 (file)
@@ -447,6 +447,7 @@ void AssemblyHelpers::loadProperty(GPRReg object, GPRReg offset, JSValueRegs res
 void AssemblyHelpers::emitLoadStructure(RegisterID source, RegisterID dest, RegisterID scratch)
 {
 #if USE(JSVALUE64)
+    ASSERT(dest != scratch);
     load32(MacroAssembler::Address(source, JSCell::structureIDOffset()), dest);
     loadPtr(vm()->heap.structureIDTable().base(), scratch);
     loadPtr(MacroAssembler::BaseIndex(scratch, dest, MacroAssembler::TimesEight), dest);
index 68edc38..d60bcdf 100644 (file)
@@ -98,7 +98,7 @@ void ArrayPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("reverse", arrayProtoFuncReverse, DontEnum, 0);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPublicName(), arrayProtoFuncShift, DontEnum, 0);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->builtinNames().shiftPrivateName(), arrayProtoFuncShift, DontEnum | DontDelete | ReadOnly, 0);
-    JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->slice, arrayProtoFuncSlice, DontEnum, 2);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->slice, arrayProtoFuncSlice, DontEnum, 2, ArraySliceIntrinsic);
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("sort", arrayPrototypeSortCodeGenerator, DontEnum);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("splice", arrayProtoFuncSplice, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("unshift", arrayProtoFuncUnShift, DontEnum, 1);
@@ -191,21 +191,12 @@ static ALWAYS_INLINE void setLength(ExecState* exec, VM& vm, JSObject* obj, unsi
         throwTypeError(exec, scope, ASCIILiteral(ReadonlyPropertyWriteError));
 }
 
-inline bool speciesWatchpointsValid(ExecState* exec, JSObject* thisObject)
+inline bool speciesWatchpointIsValid(JSObject* thisObject)
 {
-    VM& vm = exec->vm();
-    auto scope = DECLARE_THROW_SCOPE(vm);
-
     ArrayPrototype* arrayPrototype = thisObject->globalObject()->arrayPrototype();
-    ArrayPrototype::SpeciesWatchpointStatus status = arrayPrototype->speciesWatchpointStatus();
-    if (UNLIKELY(status == ArrayPrototype::SpeciesWatchpointStatus::Uninitialized)) {
-        status = arrayPrototype->attemptToInitializeSpeciesWatchpoint(exec);
-        RETURN_IF_EXCEPTION(scope, false);
-    }
-    ASSERT(status != ArrayPrototype::SpeciesWatchpointStatus::Uninitialized);
     return !thisObject->hasCustomProperties()
         && arrayPrototype == thisObject->getPrototypeDirect()
-        && status == ArrayPrototype::SpeciesWatchpointStatus::Initialized;
+        && arrayPrototype->globalObject()->arraySpeciesWatchpoint().isStillValid();
 }
 
 enum class SpeciesConstructResult {
@@ -230,8 +221,7 @@ static ALWAYS_INLINE std::pair<SpeciesConstructResult, JSObject*> speciesConstru
     if (LIKELY(thisIsArray)) {
         // Fast path in the normal case where the user has not set an own constructor and the Array.prototype.constructor is normal.
         // We need prototype check for subclasses of Array, which are Array objects but have a different prototype by default.
-        bool isValid = speciesWatchpointsValid(exec, thisObject);
-        RETURN_IF_EXCEPTION(scope, exceptionResult());
+        bool isValid = speciesWatchpointIsValid(thisObject);
         if (LIKELY(isValid))
             return std::make_pair(SpeciesConstructResult::FastPath, nullptr);
 
@@ -920,29 +910,29 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncShift(ExecState* exec)
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
 {
-    // http://developer.netscape.com/docs/manuals/js/client/jsref/array.htm#1193713 or 15.4.4.10
+    // https://tc39.github.io/ecma262/#sec-array.prototype.slice
     VM& vm = exec->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
     JSObject* thisObj = exec->thisValue().toThis(exec, StrictMode).toObject(exec);
     ASSERT(!!scope.exception() == !thisObj);
     if (UNLIKELY(!thisObj))
-        return encodedJSValue();
+        return { };
     unsigned length = getLength(exec, thisObj);
-    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    RETURN_IF_EXCEPTION(scope, { });
 
     unsigned begin = argumentClampedIndexFromStartOrEnd(exec, 0, length);
-    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    RETURN_IF_EXCEPTION(scope, { });
     unsigned end = argumentClampedIndexFromStartOrEnd(exec, 1, length, length);
-    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    RETURN_IF_EXCEPTION(scope, { });
 
     std::pair<SpeciesConstructResult, JSObject*> speciesResult = speciesConstructArray(exec, thisObj, end - begin);
     // We can only get an exception if we call some user function.
     ASSERT(!!scope.exception() == (speciesResult.first == SpeciesConstructResult::Exception));
     if (UNLIKELY(speciesResult.first == SpeciesConstructResult::Exception))
-        return encodedJSValue();
+        return { };
 
     bool okToDoFastPath = speciesResult.first == SpeciesConstructResult::FastPath && isJSArray(thisObj) && length == getLength(exec, thisObj);
-    RETURN_IF_EXCEPTION(scope, encodedJSValue());
+    RETURN_IF_EXCEPTION(scope, { });
     if (LIKELY(okToDoFastPath)) {
         if (JSArray* result = asArray(thisObj)->fastSlice(*exec, begin, end - begin))
             return JSValue::encode(result);
@@ -953,16 +943,16 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncSlice(ExecState* exec)
         result = speciesResult.second;
     else {
         result = constructEmptyArray(exec, nullptr, end - begin);
-        RETURN_IF_EXCEPTION(scope, encodedJSValue());
+        RETURN_IF_EXCEPTION(scope, { });
     }
 
     unsigned n = 0;
     for (unsigned k = begin; k < end; k++, n++) {
         JSValue v = getProperty(exec, thisObj, k);
-        RETURN_IF_EXCEPTION(scope, encodedJSValue());
+        RETURN_IF_EXCEPTION(scope, { });
         if (v) {
             result->putDirectIndex(exec, n, v, 0, PutDirectIndexShouldThrow);
-            RETURN_IF_EXCEPTION(scope, encodedJSValue());
+            RETURN_IF_EXCEPTION(scope, { });
         }
     }
     scope.release();
@@ -1249,8 +1239,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoPrivateFuncConcatMemcpy(ExecState* exec)
         return JSValue::encode(jsNull());
 
     // We need to check the species constructor here since checking it in the JS wrapper is too expensive for the non-optimizing tiers.
-    bool isValid = speciesWatchpointsValid(exec, firstArray);
-    ASSERT(!scope.exception() || !isValid);
+    bool isValid = speciesWatchpointIsValid(firstArray);
     if (UNLIKELY(!isValid))
         return JSValue::encode(jsNull());
 
@@ -1342,15 +1331,18 @@ private:
     ArrayPrototype* m_arrayPrototype;
 };
 
-ArrayPrototype::SpeciesWatchpointStatus ArrayPrototype::attemptToInitializeSpeciesWatchpoint(ExecState* exec)
+void ArrayPrototype::initializeSpeciesWatchpoint(ExecState* exec)
 {
-    ASSERT(m_speciesWatchpointStatus == SpeciesWatchpointStatus::Uninitialized);
-
     VM& vm = exec->vm();
+
+    RELEASE_ASSERT(!m_constructorWatchpoint);
+    RELEASE_ASSERT(!m_constructorSpeciesWatchpoint);
+
     auto scope = DECLARE_THROW_SCOPE(vm);
+    UNUSED_PARAM(scope);
 
     if (verbose)
-        dataLog("Attempting to initialize Array species watchpoints for Array.prototype: ", pointerDump(this), " with structure: ", pointerDump(this->structure()), "\nand Array: ", pointerDump(this->globalObject()->arrayConstructor()), " with structure: ", pointerDump(this->globalObject()->arrayConstructor()->structure()), "\n");
+        dataLog("Initializing Array species watchpoints for Array.prototype: ", pointerDump(this), " with structure: ", pointerDump(this->structure()), "\nand Array: ", pointerDump(this->globalObject()->arrayConstructor()), " with structure: ", pointerDump(this->globalObject()->arrayConstructor()->structure()), "\n");
     // First we need to make sure that the Array.prototype.constructor property points to Array
     // and that Array[Symbol.species] is the primordial GetterSetter.
 
@@ -1364,24 +1356,22 @@ ArrayPrototype::SpeciesWatchpointStatus ArrayPrototype::attemptToInitializeSpeci
     ArrayConstructor* arrayConstructor = globalObject->arrayConstructor();
 
     PropertySlot constructorSlot(this, PropertySlot::InternalMethodType::VMInquiry);
-    JSValue(this).get(exec, vm.propertyNames->constructor, constructorSlot);
-    if (UNLIKELY(scope.exception())
-        || constructorSlot.slotBase() != this
-        || !constructorSlot.isCacheableValue()
-        || constructorSlot.getValue(exec, vm.propertyNames->constructor) != arrayConstructor)
-        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;
+    this->getOwnPropertySlot(this, exec, vm.propertyNames->constructor, constructorSlot);
+    ASSERT(!scope.exception());
+    ASSERT(constructorSlot.slotBase() == this);
+    ASSERT(constructorSlot.isCacheableValue());
+    RELEASE_ASSERT(constructorSlot.getValue(exec, vm.propertyNames->constructor) == arrayConstructor);
 
     Structure* constructorStructure = arrayConstructor->structure(vm);
     if (constructorStructure->isDictionary())
         constructorStructure = constructorStructure->flattenDictionaryStructure(vm, arrayConstructor);
 
     PropertySlot speciesSlot(arrayConstructor, PropertySlot::InternalMethodType::VMInquiry);
-    JSValue(arrayConstructor).get(exec, vm.propertyNames->speciesSymbol, speciesSlot);
-    if (UNLIKELY(scope.exception())
-        || speciesSlot.slotBase() != arrayConstructor
-        || !speciesSlot.isCacheableGetter()
-        || speciesSlot.getterSetter() != globalObject->speciesGetterSetter())
-        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;
+    arrayConstructor->getOwnPropertySlot(arrayConstructor, exec, vm.propertyNames->speciesSymbol, speciesSlot);
+    ASSERT(!scope.exception());
+    ASSERT(speciesSlot.slotBase() == arrayConstructor);
+    ASSERT(speciesSlot.isCacheableGetter());
+    RELEASE_ASSERT(speciesSlot.getterSetter() == globalObject->speciesGetterSetter());
 
     // Now we need to setup the watchpoints to make sure these conditions remain valid.
     prototypeStructure->startWatchingPropertyForReplacements(vm, constructorSlot.cachedOffset());
@@ -1390,16 +1380,14 @@ ArrayPrototype::SpeciesWatchpointStatus ArrayPrototype::attemptToInitializeSpeci
     ObjectPropertyCondition constructorCondition = ObjectPropertyCondition::equivalence(vm, this, this, vm.propertyNames->constructor.impl(), arrayConstructor);
     ObjectPropertyCondition speciesCondition = ObjectPropertyCondition::equivalence(vm, this, arrayConstructor, vm.propertyNames->speciesSymbol.impl(), globalObject->speciesGetterSetter());
 
-    if (!constructorCondition.isWatchable() || !speciesCondition.isWatchable())
-        return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Fired;
+    RELEASE_ASSERT(constructorCondition.isWatchable());
+    RELEASE_ASSERT(speciesCondition.isWatchable());
 
     m_constructorWatchpoint = std::make_unique<ArrayPrototypeAdaptiveInferredPropertyWatchpoint>(constructorCondition, this);
     m_constructorWatchpoint->install();
 
     m_constructorSpeciesWatchpoint = std::make_unique<ArrayPrototypeAdaptiveInferredPropertyWatchpoint>(speciesCondition, this);
     m_constructorSpeciesWatchpoint->install();
-
-    return m_speciesWatchpointStatus = SpeciesWatchpointStatus::Initialized;
 }
 
 ArrayPrototypeAdaptiveInferredPropertyWatchpoint::ArrayPrototypeAdaptiveInferredPropertyWatchpoint(const ObjectPropertyCondition& key, ArrayPrototype* prototype)
@@ -1418,7 +1406,8 @@ void ArrayPrototypeAdaptiveInferredPropertyWatchpoint::handleFire(const FireDeta
     if (verbose)
         WTF::dataLog(stringDetail, "\n");
 
-    m_arrayPrototype->m_speciesWatchpointStatus = ArrayPrototype::SpeciesWatchpointStatus::Fired;
+    JSGlobalObject* globalObject = m_arrayPrototype->globalObject();
+    globalObject->arraySpeciesWatchpoint().fireAll(globalObject->vm(), stringDetail);
 }
 
 } // namespace JSC
index f7b8432..7e4d89f 100644 (file)
@@ -49,8 +49,7 @@ public:
         return Structure::create(vm, globalObject, prototype, TypeInfo(DerivedArrayType, StructureFlags), info(), ArrayClass);
     }
 
-    SpeciesWatchpointStatus speciesWatchpointStatus() const { return m_speciesWatchpointStatus; }
-    SpeciesWatchpointStatus attemptToInitializeSpeciesWatchpoint(ExecState*);
+    void initializeSpeciesWatchpoint(ExecState*);
 
     static const bool needsDestruction = false;
     // We don't need destruction since we use a finalizer.
@@ -64,7 +63,6 @@ private:
     friend ArrayPrototypeAdaptiveInferredPropertyWatchpoint;
     std::unique_ptr<ArrayPrototypeAdaptiveInferredPropertyWatchpoint> m_constructorWatchpoint;
     std::unique_ptr<ArrayPrototypeAdaptiveInferredPropertyWatchpoint> m_constructorSpeciesWatchpoint;
-    SpeciesWatchpointStatus m_speciesWatchpointStatus { SpeciesWatchpointStatus::Uninitialized };
 };
 
 EncodedJSValue JSC_HOST_CALL arrayProtoFuncToString(ExecState*);
index e4f216a..adae5ef 100644 (file)
@@ -40,6 +40,7 @@ enum JS_EXPORT_PRIVATE Intrinsic {
     TanIntrinsic,
     ArrayPushIntrinsic,
     ArrayPopIntrinsic,
+    ArraySliceIntrinsic,
     CharCodeAtIntrinsic,
     CharAtIntrinsic,
     FromCharCodeIntrinsic,
index a70ecf4..c005d80 100644 (file)
@@ -332,6 +332,7 @@ JSGlobalObject::JSGlobalObject(VM& vm, Structure* structure, const GlobalObjectM
     , m_varInjectionWatchpoint(adoptRef(new WatchpointSet(IsWatched)))
     , m_weakRandom(Options::forceWeakRandomSeed() ? Options::forcedWeakRandomSeed() : static_cast<unsigned>(randomNumber() * (std::numeric_limits<unsigned>::max() + 1.0)))
     , m_arrayIteratorProtocolWatchpoint(IsWatched)
+    , m_arraySpeciesWatchpoint(IsWatched)
     , m_templateRegistry(vm)
     , m_evalEnabled(true)
     , m_runtimeFlags()
@@ -944,6 +945,8 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
             m_arrayPrototypeSymbolIteratorWatchpoint = std::make_unique<ArrayIteratorAdaptiveWatchpoint>(condition, this);
             m_arrayPrototypeSymbolIteratorWatchpoint->install();
         }
+
+        this->arrayPrototype()->initializeSpeciesWatchpoint(exec);
     }
 
     resetPrototype(vm, getPrototypeDirect());
index b423c49..42f0409 100644 (file)
@@ -399,9 +399,11 @@ public:
     WeakRandom m_weakRandom;
 
     InlineWatchpointSet& arrayIteratorProtocolWatchpoint() { return m_arrayIteratorProtocolWatchpoint; }
+    InlineWatchpointSet& arraySpeciesWatchpoint() { return m_arraySpeciesWatchpoint; }
     // If this hasn't been invalidated, it means the array iterator protocol
     // is not observable to user code yet.
     InlineWatchpointSet m_arrayIteratorProtocolWatchpoint;
+    InlineWatchpointSet m_arraySpeciesWatchpoint;
     std::unique_ptr<ArrayIteratorAdaptiveWatchpoint> m_arrayPrototypeSymbolIteratorWatchpoint;
     std::unique_ptr<ArrayIteratorAdaptiveWatchpoint> m_arrayIteratorPrototypeNext;