[DFG][FTL] Introduce PhantomNewRegexp and RegExpExecNonGlobalOrSticky
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 18 Jan 2018 04:17:32 +0000 (04:17 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 18 Jan 2018 04:17:32 +0000 (04:17 +0000)
https://bugs.webkit.org/show_bug.cgi?id=181535

Reviewed by Saam Barati.

JSTests:

* stress/inserted-recovery-with-set-last-index.js: Added.
(shouldBe):
(foo):
* stress/materialize-regexp-at-osr-exit.js: Added.
(shouldBe):
(test):
* stress/materialize-regexp-cyclic-regexp-at-osr-exit.js: Added.
(shouldBe):
(test):
* stress/materialize-regexp-cyclic-regexp.js: Added.
(shouldBe):
(test):
(i.switch):
* stress/materialize-regexp-cyclic.js: Added.
(shouldBe):
(test):
(i.switch):
* stress/materialize-regexp-referenced-from-phantom-regexp-cyclic.js: Added.
(bar):
(foo):
(test):
* stress/materialize-regexp-referenced-from-phantom-regexp.js: Added.
(bar):
(foo):
(test):
* stress/materialize-regexp.js: Added.
(shouldBe):
(test):
* stress/phantom-regexp-regexp-exec.js: Added.
(shouldBe):
(test):
* stress/phantom-regexp-string-match.js: Added.
(shouldBe):
(test):
* stress/regexp-last-index-sinking.js: Added.
(shouldBe):
(test):

Source/JavaScriptCore:

When executing the code like `string.match(/regexp/)`, `/regexp/` object is created every time we execute this code.
However, user rarely cares about this `/regexp/` object. Typically, it is soon discarded even if it has `lastIndex`
information. So we should not create RegExpObject for this typical case.

This patch introduces PhantomNewRegexp. We convert NewRegexp node to PhantomNewRegexp in Object Allocation Sinking (OAS)
phase. We should do this analysis in OAS phase since we track modifications to `lastIndex` in the OAS phase. Even if
`lastIndex` is modified, it may not be read by users. So we have a chance to drop this NewRegexp beacause we carefully model
SetRegExpObjectLastIndex and GetRegExpObjectLastIndex in OAS phase.

This patch is a first attempt to drop NewRegexp. So we start optimizing it with the simple step: we first drop RegExp with
non-global and non-sticky one. We can later extend this optimization for RegExp with global flag. But this is not included
in this patch.

We convert RegExpExec to RegExpExecNonGlobalOrSticky if we find that the given RegExpObject's RegExp is not global/sticky
flagged. Since we do not need to touch `lastIndex` property in this case, RegExpExecNonGlobalOrSticky just takes RegExp
instead of RegExpObject. This offers the chance to make NewRegExp unused.

We also convert RegExpMatchFast to RegExpExecNonGlobalOrSticky if its RegExpObject's RegExp is non-global and non-sticky,
since they are the same behavior.

The above optimization completely removes NewRegexp in SixSpeed's regexp-u.{es5,es6}. The resulted execution time is
somewhat pure execution time of our Yarr implementation.

                             baseline                  patched

    regex-u.es5          34.8557+-0.5963     ^      6.1507+-0.5526        ^ definitely 5.6670x faster
    regex-u.es6          89.1919+-3.3851     ^     32.0917+-0.4260        ^ definitely 2.7793x faster

This patch does not change Octane/RegExp so much since it heavily uses String.prototype.replace, which is not handled in
this patch right now. We should support StringReplace node in subsequent patches.

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGClobbersExitState.cpp:
(JSC::DFG::clobbersExitState):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::dump):
* dfg/DFGMayExit.cpp:
* dfg/DFGNode.cpp:
(JSC::DFG::Node::convertToRegExpExecNonGlobalOrSticky):
* dfg/DFGNode.h:
(JSC::DFG::Node::convertToPhantomNewRegexp):
(JSC::DFG::Node::convertToSetRegExpObjectLastIndex):
(JSC::DFG::Node::hasHeapPrediction):
(JSC::DFG::Node::hasCellOperand):
(JSC::DFG::Node::isPhantomAllocation):
(JSC::DFG::Node::hasIgnoreLastIndexIsWritable):
(JSC::DFG::Node::ignoreLastIndexIsWritable):
* dfg/DFGNodeType.h:
* dfg/DFGObjectAllocationSinkingPhase.cpp:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGPromotedHeapLocation.cpp:
(WTF::printInternal):
* dfg/DFGPromotedHeapLocation.h:
(JSC::DFG::PromotedLocationDescriptor::neededForMaterialization const):
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileNewRegexp):
(JSC::DFG::SpeculativeJIT::compileSetRegExpObjectLastIndex):
(JSC::DFG::SpeculativeJIT::compileRegExpExecNonGlobalOrSticky):
* dfg/DFGSpeculativeJIT.h:
(JSC::DFG::SpeculativeJIT::callOperation):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode):
* dfg/DFGValidate.cpp:
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileRegExpExecNonGlobalOrSticky):
(JSC::FTL::DFG::LowerDFGToB3::compileNewRegexp):
(JSC::FTL::DFG::LowerDFGToB3::compileSetRegExpObjectLastIndex):
* ftl/FTLOperations.cpp:
(JSC::FTL::operationPopulateObjectInOSR):
(JSC::FTL::operationMaterializeObjectInOSR):
* jit/JITOperations.h:
* runtime/RegExpObject.h:
(JSC::RegExpObject::create):

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

42 files changed:
JSTests/ChangeLog
JSTests/stress/inserted-recovery-with-set-last-index.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-at-osr-exit.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-cyclic-regexp-at-osr-exit.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-cyclic-regexp.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-cyclic.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-referenced-from-phantom-regexp-cyclic.js [new file with mode: 0644]
JSTests/stress/materialize-regexp-referenced-from-phantom-regexp.js [new file with mode: 0644]
JSTests/stress/materialize-regexp.js [new file with mode: 0644]
JSTests/stress/phantom-regexp-regexp-exec.js [new file with mode: 0644]
JSTests/stress/phantom-regexp-string-match.js [new file with mode: 0644]
JSTests/stress/regexp-last-index-sinking.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/DFGClobbersExitState.cpp
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGGraph.cpp
Source/JavaScriptCore/dfg/DFGMayExit.cpp
Source/JavaScriptCore/dfg/DFGNode.cpp
Source/JavaScriptCore/dfg/DFGNode.h
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGObjectAllocationSinkingPhase.cpp
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGPromotedHeapLocation.cpp
Source/JavaScriptCore/dfg/DFGPromotedHeapLocation.h
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/dfg/DFGStrengthReductionPhase.cpp
Source/JavaScriptCore/dfg/DFGValidate.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/ftl/FTLOperations.cpp
Source/JavaScriptCore/jit/JITOperations.h
Source/JavaScriptCore/runtime/RegExpObject.h

index 95e342c..67a5d83 100644 (file)
@@ -1,3 +1,48 @@
+2018-01-17  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [DFG][FTL] Introduce PhantomNewRegexp and RegExpExecNonGlobalOrSticky
+        https://bugs.webkit.org/show_bug.cgi?id=181535
+
+        Reviewed by Saam Barati.
+
+        * stress/inserted-recovery-with-set-last-index.js: Added.
+        (shouldBe):
+        (foo):
+        * stress/materialize-regexp-at-osr-exit.js: Added.
+        (shouldBe):
+        (test):
+        * stress/materialize-regexp-cyclic-regexp-at-osr-exit.js: Added.
+        (shouldBe):
+        (test):
+        * stress/materialize-regexp-cyclic-regexp.js: Added.
+        (shouldBe):
+        (test):
+        (i.switch):
+        * stress/materialize-regexp-cyclic.js: Added.
+        (shouldBe):
+        (test):
+        (i.switch):
+        * stress/materialize-regexp-referenced-from-phantom-regexp-cyclic.js: Added.
+        (bar):
+        (foo):
+        (test):
+        * stress/materialize-regexp-referenced-from-phantom-regexp.js: Added.
+        (bar):
+        (foo):
+        (test):
+        * stress/materialize-regexp.js: Added.
+        (shouldBe):
+        (test):
+        * stress/phantom-regexp-regexp-exec.js: Added.
+        (shouldBe):
+        (test):
+        * stress/phantom-regexp-string-match.js: Added.
+        (shouldBe):
+        (test):
+        * stress/regexp-last-index-sinking.js: Added.
+        (shouldBe):
+        (test):
+
 2018-01-17  Saam Barati  <sbarati@apple.com>
 
         Disable Atomics when SharedArrayBuffer isn’t enabled
diff --git a/JSTests/stress/inserted-recovery-with-set-last-index.js b/JSTests/stress/inserted-recovery-with-set-last-index.js
new file mode 100644 (file)
index 0000000..3a5ec25
--- /dev/null
@@ -0,0 +1,28 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function foo(p) {
+    var o = /Hello/;
+    if (p) {
+        var res = /World/;
+        res.lastIndex = o;
+        return res;
+    }
+}
+
+noInline(foo);
+
+var array = new Array(1000);
+for (var i = 0; i < 4000000; ++i) {
+    var o = foo(i & 0x1);
+    if (i & 0x1) {
+        shouldBe(o instanceof RegExp, true);
+        shouldBe(o.toString(), "/World/");
+        shouldBe(o.lastIndex.toString(), "/Hello/");
+    }
+    array[i % array.length] = o;
+}
+
diff --git a/JSTests/stress/materialize-regexp-at-osr-exit.js b/JSTests/stress/materialize-regexp-at-osr-exit.js
new file mode 100644 (file)
index 0000000..1beef52
--- /dev/null
@@ -0,0 +1,24 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(flag)
+{
+    var regexp = /hello world/;
+    regexp.lastIndex = "Cocoa";
+    if (flag) {
+        OSRExit();
+        return regexp;
+    }
+    return regexp.lastIndex;
+}
+noInline(test);
+
+for (var i = 0; i < 1e5; ++i)
+    shouldBe(test(false), "Cocoa");
+
+var regexp = test(true);
+shouldBe(regexp instanceof RegExp, true);
+shouldBe(regexp.lastIndex, "Cocoa");
diff --git a/JSTests/stress/materialize-regexp-cyclic-regexp-at-osr-exit.js b/JSTests/stress/materialize-regexp-cyclic-regexp-at-osr-exit.js
new file mode 100644 (file)
index 0000000..7b6eb2f
--- /dev/null
@@ -0,0 +1,28 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(num)
+{
+    var regexp = /hello world/;
+    var world = /World/;
+    regexp.lastIndex = world;
+    world.lastIndex = regexp;
+    if (num === 0) {
+        OSRExit();
+        return regexp;
+    }
+    return 42;
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i)
+    shouldBe(test(1), 42);
+
+var regexp = test(0);
+shouldBe(regexp instanceof RegExp, true);
+shouldBe(regexp.toString(), "/hello world/");
+shouldBe(regexp.lastIndex instanceof RegExp, true);
+shouldBe(regexp.lastIndex.toString(), "/World/");
diff --git a/JSTests/stress/materialize-regexp-cyclic-regexp.js b/JSTests/stress/materialize-regexp-cyclic-regexp.js
new file mode 100644 (file)
index 0000000..1d6e5a3
--- /dev/null
@@ -0,0 +1,46 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(num)
+{
+    var regexp = /hello world/;
+    var world = /World/;
+    regexp.lastIndex = world;
+    world.lastIndex = regexp;
+    if (num === 0)
+        return regexp;
+    if (num === 1)
+        return regexp.lastIndex;
+    return regexp.lastIndex.lastIndex;
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    var num = i % 3;
+    switch (num) {
+    case 0:
+        var regexp = test(num);
+        shouldBe(regexp instanceof RegExp, true);
+        shouldBe(regexp.toString(), "/hello world/");
+        shouldBe(regexp.lastIndex instanceof RegExp, true);
+        shouldBe(regexp.lastIndex.toString(), "/World/");
+        break;
+    case 1:
+        var regexp = test(num);
+        shouldBe(regexp instanceof RegExp, true);
+        shouldBe(regexp.toString(), "/World/");
+        shouldBe(regexp.lastIndex instanceof RegExp, true);
+        shouldBe(regexp.lastIndex.toString(), "/hello world/");
+        break;
+    case 2:
+        var regexp = test(num);
+        shouldBe(regexp instanceof RegExp, true);
+        shouldBe(regexp.toString(), "/hello world/");
+        shouldBe(regexp.lastIndex instanceof RegExp, true);
+        shouldBe(regexp.lastIndex.toString(), "/World/");
+        break;
+    }
+}
diff --git a/JSTests/stress/materialize-regexp-cyclic.js b/JSTests/stress/materialize-regexp-cyclic.js
new file mode 100644 (file)
index 0000000..1097041
--- /dev/null
@@ -0,0 +1,39 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(num)
+{
+    var regexp = /hello world/;
+    regexp.lastIndex = { ok: regexp, value: 42 };
+    if (num === 0)
+        return regexp;
+    if (num === 1)
+        return regexp.lastIndex;
+    return regexp.lastIndex.value;
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    var num = i % 3;
+    switch (num) {
+    case 0:
+        var regexp = test(num);
+        shouldBe(regexp instanceof RegExp, true);
+        shouldBe(typeof regexp.lastIndex, "object");
+        shouldBe(regexp.lastIndex.ok, regexp);
+        break;
+    case 1:
+        var object = test(num);
+        shouldBe(object.value, 42);
+        shouldBe(object.ok instanceof RegExp, true);
+        shouldBe(object.ok.lastIndex, object);
+        break;
+    case 2:
+        var value = test(num);
+        shouldBe(value, 42);
+        break;
+    }
+}
diff --git a/JSTests/stress/materialize-regexp-referenced-from-phantom-regexp-cyclic.js b/JSTests/stress/materialize-regexp-referenced-from-phantom-regexp-cyclic.js
new file mode 100644 (file)
index 0000000..1c32353
--- /dev/null
@@ -0,0 +1,43 @@
+function bar()
+{
+}
+
+noInline(bar);
+
+function foo(p, x)
+{
+    var a = /Hello/;
+    a.lastIndex = 1;
+    var b = /World/;
+    b.lastIndex = a;
+    var c = /World/;
+    c.lastIndex = a;
+    var d = /Cocoa/;
+    d.lastIndex = c;
+    a.lastIndex = d;
+
+    if (!p)
+        return 0;
+
+    bar(b);
+
+    x += 2000000000;
+
+    c.lastIndex.lastIndex = 42;
+    return b.lastIndex.lastIndex;
+}
+
+noInline(foo);
+
+function test(x)
+{
+    var result = foo(true, x);
+    if (result != 42)
+        throw "Error: bad result: " + result;
+}
+
+for (var i = 0; i < 100000; ++i)
+    test(0);
+
+test(2000000000);
+
diff --git a/JSTests/stress/materialize-regexp-referenced-from-phantom-regexp.js b/JSTests/stress/materialize-regexp-referenced-from-phantom-regexp.js
new file mode 100644 (file)
index 0000000..d11b9dd
--- /dev/null
@@ -0,0 +1,40 @@
+function bar()
+{
+}
+
+noInline(bar);
+
+function foo(p, x)
+{
+    var a = /Hello/;
+    a.lastIndex = 1;
+    var b = /World/;
+    b.lastIndex = a;
+    var c = /World/;
+    c.lastIndex = a;
+
+    if (!p)
+        return 0;
+
+    bar(b);
+
+    x += 2000000000;
+
+    c.lastIndex.lastIndex = 42;
+    return b.lastIndex.lastIndex;
+}
+
+noInline(foo);
+
+function test(x)
+{
+    var result = foo(true, x);
+    if (result != 42)
+        throw "Error: bad result: " + result;
+}
+
+for (var i = 0; i < 100000; ++i)
+    test(0);
+
+test(2000000000);
+
diff --git a/JSTests/stress/materialize-regexp.js b/JSTests/stress/materialize-regexp.js
new file mode 100644 (file)
index 0000000..1fccf8d
--- /dev/null
@@ -0,0 +1,24 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(flag)
+{
+    var regexp = /hello world/;
+    regexp.lastIndex = "Cocoa";
+    if (flag)
+        return regexp;
+    return regexp.lastIndex;
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    if (i & 0x1) {
+        var regexp = test(true);
+        shouldBe(regexp instanceof RegExp, true);
+        shouldBe(regexp.lastIndex, "Cocoa");
+    } else
+        shouldBe(test(false), "Cocoa");
+}
diff --git a/JSTests/stress/phantom-regexp-regexp-exec.js b/JSTests/stress/phantom-regexp-regexp-exec.js
new file mode 100644 (file)
index 0000000..c37782b
--- /dev/null
@@ -0,0 +1,26 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(string, flag)
+{
+    var regexp = /oa/;
+    var result = regexp.exec(string);
+    if (flag)
+        return regexp;
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e5; ++i) {
+    if (i & 1) {
+        var result = test("Cocoa", true);
+        shouldBe(result instanceof RegExp, true);
+    } else {
+        var result = test("Cocoa", false);
+        shouldBe(result.input, "Cocoa");
+        shouldBe(result[0], "oa");
+    }
+}
diff --git a/JSTests/stress/phantom-regexp-string-match.js b/JSTests/stress/phantom-regexp-string-match.js
new file mode 100644 (file)
index 0000000..345089f
--- /dev/null
@@ -0,0 +1,26 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(string, flag)
+{
+    var regexp = /oa/;
+    var result = string.match(regexp);
+    if (flag)
+        return regexp;
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e5; ++i) {
+    if (i & 1) {
+        var result = test("Cocoa", true);
+        shouldBe(result instanceof RegExp, true);
+    } else {
+        var result = test("Cocoa", false);
+        shouldBe(result.input, "Cocoa");
+        shouldBe(result[0], "oa");
+    }
+}
diff --git a/JSTests/stress/regexp-last-index-sinking.js b/JSTests/stress/regexp-last-index-sinking.js
new file mode 100644 (file)
index 0000000..521fa8e
--- /dev/null
@@ -0,0 +1,23 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test(num)
+{
+    var regexp = /regexp/;
+    if (num & 1)
+        regexp.lastIndex = 42;
+    else
+        regexp.lastIndex = 2;
+    return regexp.lastIndex;
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i) {
+    if (i & 1)
+        shouldBe(test(i), 42);
+    else
+        shouldBe(test(i), 2);
+}
index 64860e7..3cfdf6b 100644 (file)
@@ -1,5 +1,106 @@
 2018-01-17  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [DFG][FTL] Introduce PhantomNewRegexp and RegExpExecNonGlobalOrSticky
+        https://bugs.webkit.org/show_bug.cgi?id=181535
+
+        Reviewed by Saam Barati.
+
+        When executing the code like `string.match(/regexp/)`, `/regexp/` object is created every time we execute this code.
+        However, user rarely cares about this `/regexp/` object. Typically, it is soon discarded even if it has `lastIndex`
+        information. So we should not create RegExpObject for this typical case.
+
+        This patch introduces PhantomNewRegexp. We convert NewRegexp node to PhantomNewRegexp in Object Allocation Sinking (OAS)
+        phase. We should do this analysis in OAS phase since we track modifications to `lastIndex` in the OAS phase. Even if
+        `lastIndex` is modified, it may not be read by users. So we have a chance to drop this NewRegexp beacause we carefully model
+        SetRegExpObjectLastIndex and GetRegExpObjectLastIndex in OAS phase.
+
+        This patch is a first attempt to drop NewRegexp. So we start optimizing it with the simple step: we first drop RegExp with
+        non-global and non-sticky one. We can later extend this optimization for RegExp with global flag. But this is not included
+        in this patch.
+
+        We convert RegExpExec to RegExpExecNonGlobalOrSticky if we find that the given RegExpObject's RegExp is not global/sticky
+        flagged. Since we do not need to touch `lastIndex` property in this case, RegExpExecNonGlobalOrSticky just takes RegExp
+        instead of RegExpObject. This offers the chance to make NewRegExp unused.
+
+        We also convert RegExpMatchFast to RegExpExecNonGlobalOrSticky if its RegExpObject's RegExp is non-global and non-sticky,
+        since they are the same behavior.
+
+        The above optimization completely removes NewRegexp in SixSpeed's regexp-u.{es5,es6}. The resulted execution time is
+        somewhat pure execution time of our Yarr implementation.
+
+                                     baseline                  patched
+
+            regex-u.es5          34.8557+-0.5963     ^      6.1507+-0.5526        ^ definitely 5.6670x faster
+            regex-u.es6          89.1919+-3.3851     ^     32.0917+-0.4260        ^ definitely 2.7793x faster
+
+        This patch does not change Octane/RegExp so much since it heavily uses String.prototype.replace, which is not handled in
+        this patch right now. We should support StringReplace node in subsequent patches.
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGClobbersExitState.cpp:
+        (JSC::DFG::clobbersExitState):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::dump):
+        * dfg/DFGMayExit.cpp:
+        * dfg/DFGNode.cpp:
+        (JSC::DFG::Node::convertToRegExpExecNonGlobalOrSticky):
+        * dfg/DFGNode.h:
+        (JSC::DFG::Node::convertToPhantomNewRegexp):
+        (JSC::DFG::Node::convertToSetRegExpObjectLastIndex):
+        (JSC::DFG::Node::hasHeapPrediction):
+        (JSC::DFG::Node::hasCellOperand):
+        (JSC::DFG::Node::isPhantomAllocation):
+        (JSC::DFG::Node::hasIgnoreLastIndexIsWritable):
+        (JSC::DFG::Node::ignoreLastIndexIsWritable):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGObjectAllocationSinkingPhase.cpp:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGPromotedHeapLocation.cpp:
+        (WTF::printInternal):
+        * dfg/DFGPromotedHeapLocation.h:
+        (JSC::DFG::PromotedLocationDescriptor::neededForMaterialization const):
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileNewRegexp):
+        (JSC::DFG::SpeculativeJIT::compileSetRegExpObjectLastIndex):
+        (JSC::DFG::SpeculativeJIT::compileRegExpExecNonGlobalOrSticky):
+        * dfg/DFGSpeculativeJIT.h:
+        (JSC::DFG::SpeculativeJIT::callOperation):
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGStrengthReductionPhase.cpp:
+        (JSC::DFG::StrengthReductionPhase::handleNode):
+        * dfg/DFGValidate.cpp:
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileRegExpExecNonGlobalOrSticky):
+        (JSC::FTL::DFG::LowerDFGToB3::compileNewRegexp):
+        (JSC::FTL::DFG::LowerDFGToB3::compileSetRegExpObjectLastIndex):
+        * ftl/FTLOperations.cpp:
+        (JSC::FTL::operationPopulateObjectInOSR):
+        (JSC::FTL::operationMaterializeObjectInOSR):
+        * jit/JITOperations.h:
+        * runtime/RegExpObject.h:
+        (JSC::RegExpObject::create):
+
+2018-01-17  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         [FTL] Remove unused helper functions to convert node to PutHint
         https://bugs.webkit.org/show_bug.cgi?id=181775
 
index 2eaa5c4..a35bc70 100644 (file)
@@ -1935,11 +1935,15 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
     }
             
     case RegExpExec:
-        if (node->child2().useKind() == RegExpObjectUse
-            && node->child3().useKind() == StringUse) {
-            // This doesn't clobber the world since there are no conversions to perform.
-        } else
-            clobberWorld(node->origin.semantic, clobberLimit);
+    case RegExpExecNonGlobalOrSticky:
+        if (node->op() == RegExpExec) {
+            if (node->child2().useKind() == RegExpObjectUse
+                && node->child3().useKind() == StringUse) {
+                // This doesn't clobber the world since there are no conversions to perform.
+            } else
+                clobberWorld(node->origin.semantic, clobberLimit);
+        }
+
         if (JSValue globalObjectValue = forNode(node->child1()).m_value) {
             if (JSGlobalObject* globalObject = jsDynamicCast<JSGlobalObject*>(m_vm, globalObjectValue)) {
                 if (!globalObject->isHavingABadTime()) {
@@ -2254,6 +2258,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
     case PhantomSpread:
     case PhantomNewArrayWithSpread:
     case PhantomNewArrayBuffer:
+    case PhantomNewRegexp:
     case BottomValue:
         m_state.setDidClobber(true); // Prevent constant folding.
         // This claims to return bottom.
index feb0604..b2bc244 100644 (file)
@@ -4499,7 +4499,7 @@ void ByteCodeParser::parseBlock(unsigned limit)
         case op_new_regexp: {
             RegExp* regexp = m_inlineStackTop->m_codeBlock->regexp(currentInstruction[2].u.operand);
             FrozenValue* frozen = m_graph.freezeStrong(regexp);
-            set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(NewRegexp, OpInfo(frozen)));
+            set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(NewRegexp, OpInfo(frozen), jsConstant(jsNumber(0))));
             NEXT_OPCODE(op_new_regexp);
         }
 
index 5518b8a..4d666e9 100644 (file)
@@ -1475,6 +1475,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case PhantomNewAsyncGeneratorFunction:
     case PhantomCreateActivation:
     case MaterializeCreateActivation:
+    case PhantomNewRegexp:
         read(HeapObjectCount);
         write(HeapObjectCount);
         return;
@@ -1504,6 +1505,11 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         write(Heap);
         return;
 
+    case RegExpExecNonGlobalOrSticky:
+        read(RegExpState);
+        write(RegExpState);
+        return;
+
     case StringReplace:
     case StringReplaceRegExp:
         if (node->child1().useKind() == StringUse
index 6609f0f..2e4c963 100644 (file)
@@ -67,6 +67,7 @@ bool clobbersExitState(Graph& graph, Node* node)
     case PhantomNewAsyncFunction:
     case PhantomCreateActivation:
     case MaterializeCreateActivation:
+    case PhantomNewRegexp:
     case CountExecution:
     case SuperSamplerBegin:
     case SuperSamplerEnd:
index 02ea564..9a66ff2 100644 (file)
@@ -137,6 +137,7 @@ bool doesGC(Graph& graph, Node* node)
     case AssertNotEmpty:
     case CheckStringIdent:
     case RegExpExec:
+    case RegExpExecNonGlobalOrSticky:
     case RegExpTest:
     case RegExpMatchFast:
     case CompareLess:
@@ -272,6 +273,7 @@ bool doesGC(Graph& graph, Node* node)
     case PhantomNewArrayBuffer:
     case PhantomSpread:
     case PhantomClonedArguments:
+    case PhantomNewRegexp:
     case GetMyArgumentByVal:
     case GetMyArgumentByValOutOfBounds:
     case ForwardVarargs:
index f6c8add..3da5e74 100644 (file)
@@ -1443,7 +1443,7 @@ private:
                 
                 if (uid == vm().propertyNames->lastIndex.impl()
                     && node->child1()->shouldSpeculateRegExpObject()) {
-                    node->setOp(SetRegExpObjectLastIndex);
+                    node->convertToSetRegExpObjectLastIndex();
                     fixEdge<RegExpObjectUse>(node->child1());
                     speculateForBarrier(node->child2());
                     break;
@@ -1651,6 +1651,7 @@ private:
         case PhantomNewArrayWithSpread:
         case PhantomNewArrayBuffer:
         case PhantomClonedArguments:
+        case PhantomNewRegexp:
         case GetMyArgumentByVal:
         case GetMyArgumentByValOutOfBounds:
         case GetVectorLength:
@@ -1667,6 +1668,7 @@ private:
         case GetRegExpObjectLastIndex:
         case SetRegExpObjectLastIndex:
         case RecordRegExpCachedResult:
+        case RegExpExecNonGlobalOrSticky:
             // These are just nodes that we don't currently expect to see during fixup.
             // If we ever wanted to insert them prior to fixup, then we just have to create
             // fixup rules for them.
index 630a32c..67abe09 100644 (file)
@@ -346,6 +346,8 @@ void Graph::dump(PrintStream& out, const char* prefix, Node* node, DumpContext*
         out.print(comma, "id", data->identifierNumber, "{", identifiers()[data->identifierNumber], "}");
         out.print(", domJIT = ", RawPointer(data->domJIT));
     }
+    if (node->hasIgnoreLastIndexIsWritable())
+        out.print(comma, "ignoreLastIndexIsWritable = ", node->ignoreLastIndexIsWritable());
     if (node->isConstant())
         out.print(comma, pointerDumpInContext(node->constant(), context));
     if (node->isJump())
index 3127bde..0fdd5d9 100644 (file)
@@ -118,9 +118,15 @@ ExitMode mayExitImpl(Graph& graph, Node* node, StateType& state)
     case NewStringObject:
     case NewRegexp:
     case ToNumber:
+    case RegExpExecNonGlobalOrSticky:
         result = ExitsForExceptions;
         break;
 
+    case SetRegExpObjectLastIndex:
+        if (node->ignoreLastIndexIsWritable())
+            break;
+        return Exits;
+
     default:
         // If in doubt, return true.
         return Exits;
index d072992..a8e7700 100644 (file)
@@ -209,6 +209,16 @@ void Node::convertToCallDOM(Graph& graph)
         clearFlags(NodeMustGenerate);
 }
 
+void Node::convertToRegExpExecNonGlobalOrSticky(FrozenValue* regExp)
+{
+    ASSERT(op() == RegExpExec);
+    setOpAndDefaultFlags(RegExpExecNonGlobalOrSticky);
+    children.child1() = Edge(children.child1().node(), KnownCellUse);
+    children.child2() = Edge(children.child3().node(), StringUse);
+    children.child3() = Edge();
+    m_opInfo = regExp;
+}
+
 String Node::tryGetString(Graph& graph)
 {
     if (hasConstant())
index c5a74dd..22e7847 100644 (file)
@@ -648,6 +648,15 @@ public:
         children = AdjacencyList();
     }
 
+    void convertToPhantomNewRegexp()
+    {
+        ASSERT(m_op == NewRegexp);
+        setOpAndDefaultFlags(PhantomNewRegexp);
+        m_opInfo = OpInfoWrapper();
+        m_opInfo2 = OpInfoWrapper();
+        children = AdjacencyList();
+    }
+
     void convertPhantomToPhantomLocal()
     {
         ASSERT(m_op == Phantom && (child1()->op() == Phi || child1()->op() == SetLocal || child1()->op() == SetArgument));
@@ -727,6 +736,14 @@ public:
     void convertToDirectCall(FrozenValue*);
 
     void convertToCallDOM(Graph&);
+
+    void convertToRegExpExecNonGlobalOrSticky(FrozenValue* regExp);
+
+    void convertToSetRegExpObjectLastIndex()
+    {
+        setOp(SetRegExpObjectLastIndex);
+        m_opInfo = false;
+    }
     
     JSValue asJSValue()
     {
@@ -1563,6 +1580,7 @@ public:
         case ArrayPop:
         case ArrayPush:
         case RegExpExec:
+        case RegExpExecNonGlobalOrSticky:
         case RegExpTest:
         case RegExpMatchFast:
         case GetGlobalVar:
@@ -1645,6 +1663,7 @@ public:
         case DirectTailCall:
         case DirectConstruct:
         case DirectTailCallInlinedCaller:
+        case RegExpExecNonGlobalOrSticky:
             return true;
         default:
             return false;
@@ -1909,6 +1928,7 @@ public:
         case PhantomNewAsyncFunction:
         case PhantomNewAsyncGeneratorFunction:
         case PhantomCreateActivation:
+        case PhantomNewRegexp:
             return true;
         default:
             return false;
@@ -2654,6 +2674,17 @@ public:
         return m_opInfo.as<int32_t>();
     }
 
+    bool hasIgnoreLastIndexIsWritable()
+    {
+        return op() == SetRegExpObjectLastIndex;
+    }
+
+    bool ignoreLastIndexIsWritable()
+    {
+        ASSERT(hasIgnoreLastIndexIsWritable());
+        return m_opInfo.as<uint32_t>();
+    }
+
     uint32_t errorType()
     {
         ASSERT(op() == ThrowStaticError);
index 05f722d..84a7b94 100644 (file)
@@ -268,6 +268,7 @@ namespace JSC { namespace DFG {
     \
     /* Optimizations for regular expression matching. */\
     macro(RegExpExec, NodeResultJS | NodeMustGenerate) \
+    macro(RegExpExecNonGlobalOrSticky, NodeResultJS) \
     macro(RegExpTest, NodeResultJS | NodeMustGenerate) \
     macro(RegExpMatchFast, NodeResultJS | NodeMustGenerate) \
     macro(StringReplace, NodeResultJS | NodeMustGenerate) \
@@ -332,6 +333,7 @@ namespace JSC { namespace DFG {
     macro(PhantomNewAsyncGeneratorFunction, NodeResultJS | NodeMustGenerate) \
     macro(PhantomCreateActivation, NodeResultJS | NodeMustGenerate) \
     macro(MaterializeCreateActivation, NodeResultJS | NodeHasVarArgs) \
+    macro(PhantomNewRegexp, NodeResultJS | NodeMustGenerate) \
     \
     /* Nodes for misc operations. */\
     macro(OverridesHasInstance, NodeMustGenerate | NodeResultBoolean) \
index 7763783..19ce124 100644 (file)
@@ -141,7 +141,7 @@ public:
     // once it is escaped if it still has pointers to it in order to
     // replace any use of those pointers by the corresponding
     // materialization
-    enum class Kind { Escaped, Object, Activation, Function, GeneratorFunction, AsyncFunction, AsyncGeneratorFunction };
+    enum class Kind { Escaped, Object, Activation, Function, GeneratorFunction, AsyncFunction, AsyncGeneratorFunction, RegExpObject };
 
     using Fields = HashMap<PromotedLocationDescriptor, Node*>;
 
@@ -246,6 +246,11 @@ public:
         return m_kind == Kind::Function || m_kind == Kind::GeneratorFunction || m_kind == Kind::AsyncFunction;
     }
 
+    bool isRegExpObjectAllocation() const
+    {
+        return m_kind == Kind::RegExpObject;
+    }
+
     bool operator==(const Allocation& other) const
     {
         return m_identifier == other.m_identifier
@@ -290,9 +295,14 @@ public:
         case Kind::AsyncGeneratorFunction:
             out.print("AsyncGeneratorFunction");
             break;
+
         case Kind::Activation:
             out.print("Activation");
             break;
+
+        case Kind::RegExpObject:
+            out.print("RegExpObject");
+            break;
         }
         out.print("Allocation(");
         if (!m_structures.isEmpty())
@@ -849,6 +859,14 @@ private:
             break;
         }
 
+        case NewRegexp: {
+            target = &m_heap.newAllocation(node, Allocation::Kind::RegExpObject);
+
+            writes.add(RegExpObjectRegExpPLoc, LazyNode(node->cellOperand()));
+            writes.add(RegExpObjectLastIndexPLoc, LazyNode(node->child1().node()));
+            break;
+        }
+
         case CreateActivation: {
             if (isStillValid(node->castOperand<SymbolTable*>()->singletonScope())) {
                 m_heap.escape(node->child1().node());
@@ -1032,6 +1050,26 @@ private:
                 m_heap.escape(node->child1().node());
             break;
 
+        case GetRegExpObjectLastIndex:
+            target = m_heap.onlyLocalAllocation(node->child1().node());
+            if (target && target->isRegExpObjectAllocation())
+                exactRead = RegExpObjectLastIndexPLoc;
+            else
+                m_heap.escape(node->child1().node());
+            break;
+
+        case SetRegExpObjectLastIndex:
+            target = m_heap.onlyLocalAllocation(node->child1().node());
+            if (target && target->isRegExpObjectAllocation()) {
+                writes.add(
+                    PromotedLocationDescriptor(RegExpObjectLastIndexPLoc),
+                    LazyNode(node->child2().node()));
+            } else {
+                m_heap.escape(node->child1().node());
+                m_heap.escape(node->child2().node());
+            }
+            break;
+
         case Check:
             m_graph.doToChildren(
                 node,
@@ -1508,6 +1546,16 @@ private:
                 OpInfo(symbolTable), OpInfo(data), 0, 0);
         }
 
+        case Allocation::Kind::RegExpObject: {
+            FrozenValue* regExp = allocation.identifier()->cellOperand();
+            return m_graph.addNode(
+                allocation.identifier()->prediction(), NewRegexp,
+                where->origin.withSemantic(
+                    allocation.identifier()->origin.semantic),
+                OpInfo(regExp));
+            break;
+        }
+
         default:
             DFG_CRASH(m_graph, allocation.identifier(), "Bad allocation kind");
         }
@@ -1864,9 +1912,11 @@ private:
                     case NewGeneratorFunction:
                         node->convertToPhantomNewGeneratorFunction();
                         break;
+
                     case NewAsyncGeneratorFunction:
                         node->convertToPhantomNewAsyncGeneratorFunction();
                         break;
+
                     case NewAsyncFunction:
                         node->convertToPhantomNewAsyncFunction();
                         break;
@@ -1875,6 +1925,10 @@ private:
                         node->convertToPhantomCreateActivation();
                         break;
 
+                    case NewRegexp:
+                        node->convertToPhantomNewRegexp();
+                        break;
+
                     default:
                         node->remove();
                         break;
@@ -2143,6 +2197,23 @@ private:
             break;
         }
 
+        case NewRegexp: {
+            Vector<PromotedHeapLocation> locations = m_locationsForAllocation.get(escapee);
+            ASSERT(locations.size() == 2);
+
+            PromotedHeapLocation regExp(RegExpObjectRegExpPLoc, allocation.identifier());
+            ASSERT_UNUSED(regExp, locations.contains(regExp));
+
+            PromotedHeapLocation lastIndex(RegExpObjectLastIndexPLoc, allocation.identifier());
+            ASSERT(locations.contains(lastIndex));
+            Node* value = resolve(block, lastIndex);
+            if (m_sinkCandidates.contains(value))
+                node->child1() = Edge(m_bottom);
+            else
+                node->child1() = Edge(value);
+            break;
+        }
+
         default:
             DFG_CRASH(m_graph, node, "Bad materialize op");
         }
@@ -2248,6 +2319,16 @@ private:
             break;
         }
 
+        case RegExpObjectLastIndexPLoc: {
+            return m_graph.addNode(
+                SetRegExpObjectLastIndex,
+                origin.takeValidExit(canExit),
+                OpInfo(true),
+                Edge(base, KnownCellUse),
+                value->defaultEdge());
+            break;
+        }
+
         default:
             DFG_CRASH(m_graph, base, "Bad location kind");
             break;
index 21994c6..21daef0 100644 (file)
@@ -60,6 +60,8 @@
 #include "ObjectConstructor.h"
 #include "Operations.h"
 #include "ParseInt.h"
+#include "RegExpConstructor.h"
+#include "RegExpMatchesArray.h"
 #include "RegExpObject.h"
 #include "Repatch.h"
 #include "ScopedArguments.h"
@@ -1023,6 +1025,32 @@ EncodedJSValue JIT_OPERATION operationRegExpExecGeneric(ExecState* exec, JSGloba
     return JSValue::encode(asRegExpObject(base)->exec(exec, globalObject, input));
 }
 
+EncodedJSValue JIT_OPERATION operationRegExpExecNonGlobalOrSticky(ExecState* exec, JSGlobalObject* globalObject, RegExp* regExp, JSString* string)
+{
+    SuperSamplerScope superSamplerScope(false);
+
+    VM& vm = globalObject->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    RegExpConstructor* regExpConstructor = globalObject->regExpConstructor();
+    String input = string->value(exec);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    unsigned lastIndex = 0;
+    MatchResult result;
+    JSArray* array = createRegExpMatchesArray(vm, globalObject, string, input, regExp, lastIndex, result);
+    if (!array) {
+        ASSERT(!scope.exception());
+        return JSValue::encode(jsNull());
+    }
+
+    RETURN_IF_EXCEPTION(scope, { });
+    regExpConstructor->recordMatch(vm, regExp, string, result);
+    return JSValue::encode(array);
+}
+
 EncodedJSValue JIT_OPERATION operationRegExpMatchFastString(ExecState* exec, JSGlobalObject* globalObject, RegExpObject* regExpObject, JSString* argument)
 {
     SuperSamplerScope superSamplerScope(false);
@@ -1876,6 +1904,16 @@ JSCell* JIT_OPERATION operationToIndexString(ExecState* exec, int32_t index)
     return jsString(exec, Identifier::from(exec, index).string());
 }
 
+JSCell* JIT_OPERATION operationNewRegexpWithLastIndex(ExecState* exec, JSCell* regexpPtr, EncodedJSValue encodedLastIndex)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    RegExp* regexp = static_cast<RegExp*>(regexpPtr);
+    ASSERT(regexp->isValid());
+    return RegExpObject::create(vm, exec->lexicalGlobalObject()->regExpStructure(), regexp, JSValue::decode(encodedLastIndex));
+}
+
 StringImpl* JIT_OPERATION operationResolveRope(ExecState* exec, JSString* string)
 {
     VM& vm = exec->vm();
index f7c3bdc..eeb6564 100644 (file)
@@ -85,6 +85,7 @@ EncodedJSValue JIT_OPERATION operationHasIndexedPropertyByInt(ExecState*, JSCell
 JSCell* JIT_OPERATION operationGetPropertyEnumerator(ExecState*, EncodedJSValue);
 JSCell* JIT_OPERATION operationGetPropertyEnumeratorCell(ExecState*, JSCell*);
 JSCell* JIT_OPERATION operationToIndexString(ExecState*, int32_t);
+JSCell* JIT_OPERATION operationNewRegexpWithLastIndex(ExecState*, JSCell*, EncodedJSValue) WTF_INTERNAL;
 char* JIT_OPERATION operationNewArray(ExecState*, Structure*, void*, size_t) WTF_INTERNAL;
 char* JIT_OPERATION operationNewEmptyArray(ExecState*, Structure*) WTF_INTERNAL;
 char* JIT_OPERATION operationNewArrayWithSize(ExecState*, Structure*, int32_t, Butterfly*) WTF_INTERNAL;
@@ -151,6 +152,7 @@ EncodedJSValue JIT_OPERATION operationArrayPopAndRecoverLength(ExecState*, JSArr
 EncodedJSValue JIT_OPERATION operationRegExpExecString(ExecState*, JSGlobalObject*, RegExpObject*, JSString*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationRegExpExec(ExecState*, JSGlobalObject*, RegExpObject*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationRegExpExecGeneric(ExecState*, JSGlobalObject*, EncodedJSValue, EncodedJSValue) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationRegExpExecNonGlobalOrSticky(ExecState*, JSGlobalObject*, RegExp*, JSString*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationRegExpMatchFastString(ExecState*, JSGlobalObject*, RegExpObject*, JSString*) WTF_INTERNAL;
 // These comparisons return a boolean within a size_t such that the value is zero extended to fill the register.
 size_t JIT_OPERATION operationRegExpTestString(ExecState*, JSGlobalObject*, RegExpObject*, JSString*) WTF_INTERNAL;
index ff967bf..377637e 100644 (file)
@@ -696,6 +696,7 @@ private:
         case ArrayPop:
         case ArrayPush:
         case RegExpExec:
+        case RegExpExecNonGlobalOrSticky:
         case RegExpTest:
         case RegExpMatchFast:
         case StringReplace:
@@ -1091,6 +1092,7 @@ private:
         case PhantomNewArrayWithSpread:
         case PhantomNewArrayBuffer:
         case PhantomClonedArguments:
+        case PhantomNewRegexp:
         case GetMyArgumentByVal:
         case GetMyArgumentByValOutOfBounds:
         case PutHint:
index 68a0b98..9ecb45e 100644 (file)
@@ -126,6 +126,14 @@ void printInternal(PrintStream& out, PromotedLocationKind kind)
     case NewArrayBufferPLoc:
         out.print("NewArrayBufferPLoc");
         return;
+
+    case RegExpObjectRegExpPLoc:
+        out.print("RegExpObjectRegExpPLoc");
+        return;
+
+    case RegExpObjectLastIndexPLoc:
+        out.print("RegExpObjectLastIndexPLoc");
+        return;
     }
     
     RELEASE_ASSERT_NOT_REACHED();
index 5e747f9..38f2495 100644 (file)
@@ -64,6 +64,8 @@ enum PromotedLocationKind {
     SpreadPLoc,
     NewArrayWithSpreadArgumentPLoc,
     NewArrayBufferPLoc,
+    RegExpObjectRegExpPLoc,
+    RegExpObjectLastIndexPLoc,
 };
 
 class PromotedLocationDescriptor {
@@ -117,6 +119,7 @@ public:
         switch (kind()) {
         case NamedPropertyPLoc:
         case ClosureVarPLoc:
+        case RegExpObjectLastIndexPLoc:
             return false;
 
         default:
index c4b5256..34b95cc 100644 (file)
@@ -261,6 +261,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case AssertNotEmpty:
     case CheckStringIdent:
     case RegExpExec:
+    case RegExpExecNonGlobalOrSticky:
     case RegExpTest:
     case RegExpMatchFast:
     case CompareLess:
@@ -393,6 +394,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case PhantomNewAsyncGeneratorFunction:
     case PhantomNewAsyncFunction:
     case PhantomCreateActivation:
+    case PhantomNewRegexp:
     case PutHint:
     case CheckStructureImmediate:
     case MaterializeNewObject:
index 432b1ca..5b3e8eb 100644 (file)
@@ -9075,10 +9075,12 @@ void SpeculativeJIT::compileNewRegexp(Node* node)
     GPRTemporary result(this);
     GPRTemporary scratch1(this);
     GPRTemporary scratch2(this);
+    JSValueOperand lastIndex(this, node->child1());
 
     GPRReg resultGPR = result.gpr();
     GPRReg scratch1GPR = scratch1.gpr();
     GPRReg scratch2GPR = scratch2.gpr();
+    JSValueRegs lastIndexRegs = lastIndex.jsValueRegs();
 
     JITCompiler::JumpList slowPath;
 
@@ -9090,11 +9092,11 @@ void SpeculativeJIT::compileNewRegexp(Node* node)
     m_jit.storePtr(
         TrustedImmPtr(node->cellOperand()),
         CCallHelpers::Address(resultGPR, RegExpObject::offsetOfRegExp()));
-    m_jit.storeTrustedValue(jsNumber(0), CCallHelpers::Address(resultGPR, RegExpObject::offsetOfLastIndex()));
+    m_jit.storeValue(lastIndexRegs, CCallHelpers::Address(resultGPR, RegExpObject::offsetOfLastIndex()));
     m_jit.store8(TrustedImm32(true), CCallHelpers::Address(resultGPR, RegExpObject::offsetOfLastIndexIsWritable()));
     m_jit.mutatorFence(*m_jit.vm());
 
-    addSlowPathGenerator(slowPathCall(slowPath, this, operationNewRegexp, resultGPR, regexp));
+    addSlowPathGenerator(slowPathCall(slowPath, this, operationNewRegexpWithLastIndex, resultGPR, regexp, lastIndexRegs));
 
     cellResult(resultGPR, node);
 }
@@ -10421,12 +10423,16 @@ void SpeculativeJIT::compileSetRegExpObjectLastIndex(Node* node)
     JSValueOperand value(this, node->child2());
     GPRReg regExpGPR = regExp.gpr();
     JSValueRegs valueRegs = value.jsValueRegs();
-    speculateRegExpObject(node->child1(), regExpGPR);
-    speculationCheck(
-        ExoticObjectMode, JSValueRegs(), nullptr,
-        m_jit.branchTest8(
-            JITCompiler::Zero,
-            JITCompiler::Address(regExpGPR, RegExpObject::offsetOfLastIndexIsWritable())));
+
+    if (!node->ignoreLastIndexIsWritable()) {
+        speculateRegExpObject(node->child1(), regExpGPR);
+        speculationCheck(
+            ExoticObjectMode, JSValueRegs(), nullptr,
+            m_jit.branchTest8(
+                JITCompiler::Zero,
+                JITCompiler::Address(regExpGPR, RegExpObject::offsetOfLastIndexIsWritable())));
+    }
+
     m_jit.storeValue(valueRegs, JITCompiler::Address(regExpGPR, RegExpObject::offsetOfLastIndex()));
     noResult(node);
 }
@@ -10620,6 +10626,26 @@ void SpeculativeJIT::compileStringReplace(Node* node)
         m_jit.decrementSuperSamplerCount();
 }
 
+void SpeculativeJIT::compileRegExpExecNonGlobalOrSticky(Node* node)
+{
+    SpeculateCellOperand globalObject(this, node->child1());
+    SpeculateCellOperand argument(this, node->child2());
+    GPRReg globalObjectGPR = globalObject.gpr();
+    GPRReg argumentGPR = argument.gpr();
+
+    speculateString(node->child2(), argumentGPR);
+
+    flushRegisters();
+    JSValueRegsFlushedCallResult result(this);
+    JSValueRegs resultRegs = result.regs();
+    callOperation(
+        operationRegExpExecNonGlobalOrSticky, resultRegs,
+        globalObjectGPR, TrustedImmPtr(node->cellOperand()), argumentGPR);
+    m_jit.exceptionCheck();
+
+    jsValueResult(resultRegs, node);
+}
+
 void SpeculativeJIT::compileRegExpMatchFast(Node* node)
 {
     SpeculateCellOperand globalObject(this, node->child1());
index 37e7727..d815805 100644 (file)
@@ -1431,6 +1431,11 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2.gpr());
         return appendCallSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_JITOperation_ECJ operation, GPRReg result, JSCell* arg1, JSValueRegs arg2)
+    {
+        m_jit.setupArgumentsWithExecState(TrustedImmPtr::weakPointer(m_jit.graph(), arg1), arg2.gpr());
+        return appendCallSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_JITOperation_ECO operation, GPRReg result, GPRReg arg1, GPRReg arg2)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2);
@@ -1578,6 +1583,11 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
         return appendCallSetResult(operation, result.payloadGPR());
     }
+    JITCompiler::Call callOperation(J_JITOperation_EGReJss operation, JSValueRegs result, GPRReg arg1, TrustedImmPtr arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
+        return appendCallSetResult(operation, result.payloadGPR());
+    }
     JITCompiler::Call callOperation(C_JITOperation_EJssReo operation, GPRReg result, GPRReg arg1, GPRReg arg2)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2);
@@ -2086,6 +2096,11 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2.payloadGPR(), arg2.tagGPR());
         return appendCallSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(C_JITOperation_ECJ operation, GPRReg result, JSCell* arg1, JSValueRegs arg2)
+    {
+        m_jit.setupArgumentsWithExecState(TrustedImmPtr::weakPointer(m_jit.graph(), arg1), arg2.payloadGPR(), arg2.tagGPR());
+        return appendCallSetResult(operation, result);
+    }
     JITCompiler::Call callOperation(C_JITOperation_ECO operation, GPRReg result, GPRReg arg1, GPRReg arg2)
     {
         m_jit.setupArgumentsWithExecState(arg1, arg2);
@@ -2321,6 +2336,11 @@ public:
         m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
         return appendCallSetResult(operation, result.payloadGPR(), result.tagGPR());
     }
+    JITCompiler::Call callOperation(J_JITOperation_EGReJss operation, JSValueRegs result, GPRReg arg1, TrustedImmPtr arg2, GPRReg arg3)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2, arg3);
+        return appendCallSetResult(operation, result.payloadGPR(), result.tagGPR());
+    }
     JITCompiler::Call callOperation(J_JITOperation_ESsiCI operation, JSValueRegs result, StructureStubInfo* stubInfo, GPRReg arg1, const UniquedStringImpl* uid)
     {
         m_jit.setupArgumentsWithExecState(TrustedImmPtr(stubInfo), arg1, TrustedImmPtr(uid));
@@ -3065,6 +3085,7 @@ public:
     void compileArrayPush(Node*);
     void compileNotifyWrite(Node*);
     void compileRegExpExec(Node*);
+    void compileRegExpExecNonGlobalOrSticky(Node*);
     void compileRegExpMatchFast(Node*);
     void compileRegExpTest(Node*);
     void compileStringReplace(Node*);
index b7bae4d..8da6737 100644 (file)
@@ -3162,6 +3162,11 @@ void SpeculativeJIT::compile(Node* node)
         compileRegExpExec(node);
         break;
     }
+
+    case RegExpExecNonGlobalOrSticky: {
+        compileRegExpExecNonGlobalOrSticky(node);
+        break;
+    }
         
     case RegExpTest: {
         compileRegExpTest(node);
@@ -5159,6 +5164,7 @@ void SpeculativeJIT::compile(Node* node)
     case PhantomNewAsyncFunction:
     case PhantomNewAsyncGeneratorFunction:
     case PhantomCreateActivation:
+    case PhantomNewRegexp:
     case PutHint:
     case CheckStructureImmediate:
     case MaterializeCreateActivation:
index f21c771..d56fcc2 100644 (file)
@@ -3433,6 +3433,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case RegExpExecNonGlobalOrSticky: {
+        compileRegExpExecNonGlobalOrSticky(node);
+        break;
+    }
+
     case RegExpTest: {
         compileRegExpTest(node);
         break;
@@ -5741,6 +5746,7 @@ void SpeculativeJIT::compile(Node* node)
     case PhantomNewAsyncFunction:
     case PhantomNewAsyncGeneratorFunction:
     case PhantomCreateActivation:
+    case PhantomNewRegexp:
     case GetMyArgumentByVal:
     case GetMyArgumentByValOutOfBounds:
     case GetVectorLength:
index c4b9a1f..322f0fa 100644 (file)
@@ -462,7 +462,9 @@ private:
         }
 
         case RegExpExec:
-        case RegExpTest: {
+        case RegExpTest:
+        case RegExpMatchFast:
+        case RegExpExecNonGlobalOrSticky: {
             JSGlobalObject* globalObject = m_node->child1()->dynamicCastConstant<JSGlobalObject*>(vm());
             if (!globalObject) {
                 if (verbose)
@@ -476,250 +478,297 @@ private:
                 break;
             }
 
-            Node* regExpObjectNode = m_node->child2().node();
-            RegExp* regExp;
-            if (RegExpObject* regExpObject = regExpObjectNode->dynamicCastConstant<RegExpObject*>(vm()))
-                regExp = regExpObject->regExp();
-            else if (regExpObjectNode->op() == NewRegexp)
-                regExp = regExpObjectNode->castOperand<RegExp*>();
-            else {
-                if (verbose)
-                    dataLog("Giving up because the regexp is unknown.\n");
-                break;
-            }
+            Node* regExpObjectNode = nullptr;
+            RegExp* regExp = nullptr;
+            if (m_node->op() == RegExpExec || m_node->op() == RegExpTest || m_node->op() == RegExpMatchFast) {
+                regExpObjectNode = m_node->child2().node();
+                if (RegExpObject* regExpObject = regExpObjectNode->dynamicCastConstant<RegExpObject*>(vm()))
+                    regExp = regExpObject->regExp();
+                else if (regExpObjectNode->op() == NewRegexp)
+                    regExp = regExpObjectNode->castOperand<RegExp*>();
+                else {
+                    if (verbose)
+                        dataLog("Giving up because the regexp is unknown.\n");
+                    break;
+                }
+            } else
+                regExp = m_node->castOperand<RegExp*>();
 
-            Node* stringNode = m_node->child3().node();
-            
-            // NOTE: This mostly already protects us from having the compiler execute a regexp
-            // operation on a ginormous string by preventing us from getting our hands on ginormous
-            // strings in the first place.
-            String string = m_node->child3()->tryGetString(m_graph);
-            if (!string) {
-                if (verbose)
-                    dataLog("Giving up because the string is unknown.\n");
-                break;
+            if (m_node->op() == RegExpMatchFast) {
+                if (!regExp->global()) {
+                    m_node->setOp(RegExpExec);
+                    m_changed = true;
+                    // Continue performing strength reduction onto RegExpExec node.
+                } else
+                    break;
             }
 
-            FrozenValue* regExpFrozenValue = m_graph.freeze(regExp);
-
-            // Refuse to do things with regular expressions that have a ginormous number of
-            // subpatterns.
-            unsigned ginormousNumberOfSubPatterns = 1000;
-            if (regExp->numSubpatterns() > ginormousNumberOfSubPatterns) {
-                if (verbose)
-                    dataLog("Giving up because of pattern limit.\n");
-                break;
-            }
+            ASSERT(m_node->op() != RegExpMatchFast);
 
-            if (m_node->op() == RegExpExec && regExp->hasNamedCaptures()) {
-                // FIXME: https://bugs.webkit.org/show_bug.cgi?id=176464
-                // Implement strength reduction optimization for named capture groups.
-                if (verbose)
-                    dataLog("Giving up because of named capture groups.\n");
-                break;
-            }
+            auto foldToConstant = [&] {
+                Node* stringNode = nullptr;
+                if (m_node->op() == RegExpExecNonGlobalOrSticky)
+                    stringNode = m_node->child2().node();
+                else
+                    stringNode = m_node->child3().node();
 
-            unsigned lastIndex;
-            if (regExp->globalOrSticky()) {
-                // This will only work if we can prove what the value of lastIndex is. To do this
-                // safely, we need to execute the insertion set so that we see any previous strength
-                // reductions. This is needed for soundness since otherwise the effectfulness of any
-                // previous strength reductions would be invisible to us.
-                executeInsertionSet();
-                lastIndex = UINT_MAX;
-                for (unsigned otherNodeIndex = m_nodeIndex; otherNodeIndex--;) {
-                    Node* otherNode = m_block->at(otherNodeIndex);
-                    if (otherNode == regExpObjectNode) {
-                        lastIndex = 0;
-                        break;
-                    }
-                    if (otherNode->op() == SetRegExpObjectLastIndex
-                        && otherNode->child1() == regExpObjectNode
-                        && otherNode->child2()->isInt32Constant()
-                        && otherNode->child2()->asInt32() >= 0) {
-                        lastIndex = static_cast<unsigned>(otherNode->child2()->asInt32());
-                        break;
-                    }
-                    if (writesOverlap(m_graph, otherNode, RegExpObject_lastIndex))
-                        break;
-                }
-                if (lastIndex == UINT_MAX) {
+                // NOTE: This mostly already protects us from having the compiler execute a regexp
+                // operation on a ginormous string by preventing us from getting our hands on ginormous
+                // strings in the first place.
+                String string = stringNode->tryGetString(m_graph);
+                if (!string) {
                     if (verbose)
-                        dataLog("Giving up because the last index is not known.\n");
-                    break;
+                        dataLog("Giving up because the string is unknown.\n");
+                    return false;
                 }
-            } else
-                lastIndex = 0;
 
-            m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
-            
-            Structure* structure;
-            if (m_node->op() == RegExpExec && regExp->hasNamedCaptures())
-                structure = globalObject->regExpMatchesArrayWithGroupsStructure();
-            else
-                structure = globalObject->regExpMatchesArrayStructure();
-
-            if (structure->indexingType() != ArrayWithContiguous) {
-                // This is further protection against a race with haveABadTime.
-                if (verbose)
-                    dataLog("Giving up because the structure has the wrong indexing type.\n");
-                break;
-            }
-            m_graph.registerStructure(structure);
-
-            RegExpConstructor* constructor = globalObject->regExpConstructor();
-            FrozenValue* constructorFrozenValue = m_graph.freeze(constructor);
-
-            MatchResult result;
-            Vector<int> ovector;
-            // We have to call the kind of match function that the main thread would have called.
-            // Otherwise, we might not have the desired Yarr code compiled, and the match will fail.
-            if (m_node->op() == RegExpExec) {
-                int position;
-                if (!regExp->matchConcurrently(vm(), string, lastIndex, position, ovector)) {
+                FrozenValue* regExpFrozenValue = m_graph.freeze(regExp);
+
+                // Refuse to do things with regular expressions that have a ginormous number of
+                // subpatterns.
+                unsigned ginormousNumberOfSubPatterns = 1000;
+                if (regExp->numSubpatterns() > ginormousNumberOfSubPatterns) {
                     if (verbose)
-                        dataLog("Giving up because match failed.\n");
-                    break;
+                        dataLog("Giving up because of pattern limit.\n");
+                    return false;
                 }
-                result.start = position;
-                result.end = ovector[1];
-            } else {
-                if (!regExp->matchConcurrently(vm(), string, lastIndex, result)) {
+
+                if ((m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) && regExp->hasNamedCaptures()) {
+                    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=176464
+                    // Implement strength reduction optimization for named capture groups.
                     if (verbose)
-                        dataLog("Giving up because match failed.\n");
-                    break;
+                        dataLog("Giving up because of named capture groups.\n");
+                    return false;
                 }
-            }
 
-            // We've constant-folded the regexp. Now we're committed to replacing RegExpExec/Test.
+                unsigned lastIndex;
+                if (regExp->globalOrSticky()) {
+                    // This will only work if we can prove what the value of lastIndex is. To do this
+                    // safely, we need to execute the insertion set so that we see any previous strength
+                    // reductions. This is needed for soundness since otherwise the effectfulness of any
+                    // previous strength reductions would be invisible to us.
+                    ASSERT(regExpObjectNode);
+                    executeInsertionSet();
+                    lastIndex = UINT_MAX;
+                    for (unsigned otherNodeIndex = m_nodeIndex; otherNodeIndex--;) {
+                        Node* otherNode = m_block->at(otherNodeIndex);
+                        if (otherNode == regExpObjectNode) {
+                            lastIndex = 0;
+                            break;
+                        }
+                        if (otherNode->op() == SetRegExpObjectLastIndex
+                            && otherNode->child1() == regExpObjectNode
+                            && otherNode->child2()->isInt32Constant()
+                            && otherNode->child2()->asInt32() >= 0) {
+                            lastIndex = static_cast<unsigned>(otherNode->child2()->asInt32());
+                            break;
+                        }
+                        if (writesOverlap(m_graph, otherNode, RegExpObject_lastIndex))
+                            break;
+                    }
+                    if (lastIndex == UINT_MAX) {
+                        if (verbose)
+                            dataLog("Giving up because the last index is not known.\n");
+                        return false;
+                    }
+                } else
+                    lastIndex = 0;
 
-            m_changed = true;
+                m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
 
-            NodeOrigin origin = m_node->origin;
+                Structure* structure;
+                if ((m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) && regExp->hasNamedCaptures())
+                    structure = globalObject->regExpMatchesArrayWithGroupsStructure();
+                else
+                    structure = globalObject->regExpMatchesArrayStructure();
 
-            m_insertionSet.insertNode(
-                m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
+                if (structure->indexingType() != ArrayWithContiguous) {
+                    // This is further protection against a race with haveABadTime.
+                    if (verbose)
+                        dataLog("Giving up because the structure has the wrong indexing type.\n");
+                    return false;
+                }
+                m_graph.registerStructure(structure);
 
-            if (m_node->op() == RegExpExec) {
-                if (result) {
-                    RegisteredStructureSet* structureSet = m_graph.addStructureSet(structure);
-
-                    // Create an array modeling the JS array that we will try to allocate. This is
-                    // basically createRegExpMatchesArray but over C++ strings instead of JSStrings.
-                    Vector<String> resultArray;
-                    resultArray.append(string.substring(result.start, result.end - result.start));
-                    for (unsigned i = 1; i <= regExp->numSubpatterns(); ++i) {
-                        int start = ovector[2 * i];
-                        if (start >= 0)
-                            resultArray.append(string.substring(start, ovector[2 * i + 1] - start));
-                        else
-                            resultArray.append(String());
+                RegExpConstructor* constructor = globalObject->regExpConstructor();
+                FrozenValue* constructorFrozenValue = m_graph.freeze(constructor);
+
+                MatchResult result;
+                Vector<int> ovector;
+                // We have to call the kind of match function that the main thread would have called.
+                // Otherwise, we might not have the desired Yarr code compiled, and the match will fail.
+                if (m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) {
+                    int position;
+                    if (!regExp->matchConcurrently(vm(), string, lastIndex, position, ovector)) {
+                        if (verbose)
+                            dataLog("Giving up because match failed.\n");
+                        return false;
+                    }
+                    result.start = position;
+                    result.end = ovector[1];
+                } else {
+                    if (!regExp->matchConcurrently(vm(), string, lastIndex, result)) {
+                        if (verbose)
+                            dataLog("Giving up because match failed.\n");
+                        return false;
                     }
+                }
+
+                // We've constant-folded the regexp. Now we're committed to replacing RegExpExec/Test.
+
+                m_changed = true;
+
+                NodeOrigin origin = m_node->origin;
 
-                    unsigned publicLength = resultArray.size();
-                    unsigned vectorLength =
-                        Butterfly::optimalContiguousVectorLength(structure, publicLength);
+                m_insertionSet.insertNode(
+                    m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
+
+                if (m_node->op() == RegExpExec || m_node->op() == RegExpExecNonGlobalOrSticky) {
+                    if (result) {
+                        RegisteredStructureSet* structureSet = m_graph.addStructureSet(structure);
+
+                        // Create an array modeling the JS array that we will try to allocate. This is
+                        // basically createRegExpMatchesArray but over C++ strings instead of JSStrings.
+                        Vector<String> resultArray;
+                        resultArray.append(string.substring(result.start, result.end - result.start));
+                        for (unsigned i = 1; i <= regExp->numSubpatterns(); ++i) {
+                            int start = ovector[2 * i];
+                            if (start >= 0)
+                                resultArray.append(string.substring(start, ovector[2 * i + 1] - start));
+                            else
+                                resultArray.append(String());
+                        }
+
+                        unsigned publicLength = resultArray.size();
+                        unsigned vectorLength =
+                            Butterfly::optimalContiguousVectorLength(structure, publicLength);
+
+                        UniquedStringImpl* indexUID = vm().propertyNames->index.impl();
+                        UniquedStringImpl* inputUID = vm().propertyNames->input.impl();
+                        unsigned indexIndex = m_graph.identifiers().ensure(indexUID);
+                        unsigned inputIndex = m_graph.identifiers().ensure(inputUID);
+
+                        unsigned firstChild = m_graph.m_varArgChildren.size();
+                        m_graph.m_varArgChildren.append(
+                            m_insertionSet.insertConstantForUse(
+                                m_nodeIndex, origin, structure, KnownCellUse));
+                        ObjectMaterializationData* data = m_graph.m_objectMaterializationData.add();
+
+                        m_graph.m_varArgChildren.append(
+                            m_insertionSet.insertConstantForUse(
+                                m_nodeIndex, origin, jsNumber(publicLength), KnownInt32Use));
+                        data->m_properties.append(PublicLengthPLoc);
+
+                        m_graph.m_varArgChildren.append(
+                            m_insertionSet.insertConstantForUse(
+                                m_nodeIndex, origin, jsNumber(vectorLength), KnownInt32Use));
+                        data->m_properties.append(VectorLengthPLoc);
+
+                        m_graph.m_varArgChildren.append(
+                            m_insertionSet.insertConstantForUse(
+                                m_nodeIndex, origin, jsNumber(result.start), UntypedUse));
+                        data->m_properties.append(
+                            PromotedLocationDescriptor(NamedPropertyPLoc, indexIndex));
+
+                        m_graph.m_varArgChildren.append(Edge(stringNode, UntypedUse));
+                        data->m_properties.append(
+                            PromotedLocationDescriptor(NamedPropertyPLoc, inputIndex));
+
+                        auto materializeString = [&] (const String& string) -> Node* {
+                            if (string.isNull())
+                                return nullptr;
+                            if (string.isEmpty()) {
+                                return m_insertionSet.insertConstant(
+                                    m_nodeIndex, origin, vm().smallStrings.emptyString());
+                            }
+                            LazyJSValue value = LazyJSValue::newString(m_graph, string);
+                            return m_insertionSet.insertNode(
+                                m_nodeIndex, SpecNone, LazyJSConstant, origin,
+                                OpInfo(m_graph.m_lazyJSValues.add(value)));
+                        };
+
+                        for (unsigned i = 0; i < resultArray.size(); ++i) {
+                            if (Node* node = materializeString(resultArray[i])) {
+                                m_graph.m_varArgChildren.append(Edge(node, UntypedUse));
+                                data->m_properties.append(
+                                    PromotedLocationDescriptor(IndexedPropertyPLoc, i));
+                            }
+                        }
+
+                        Node* resultNode = m_insertionSet.insertNode(
+                            m_nodeIndex, SpecArray, Node::VarArg, MaterializeNewObject, origin,
+                            OpInfo(structureSet), OpInfo(data), firstChild,
+                            m_graph.m_varArgChildren.size() - firstChild);
+
+                        m_node->convertToIdentityOn(resultNode);
+                    } else
+                        m_graph.convertToConstant(m_node, jsNull());
+                } else
+                    m_graph.convertToConstant(m_node, jsBoolean(!!result));
+
+                // Whether it's Exec or Test, we need to tell the constructor and RegExpObject what's up.
+                // Because SetRegExpObjectLastIndex may exit and it clobbers exit state, we do that
+                // first.
+
+                if (regExp->globalOrSticky()) {
+                    ASSERT(regExpObjectNode);
+                    m_insertionSet.insertNode(
+                        m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
+                        OpInfo(false),
+                        Edge(regExpObjectNode, RegExpObjectUse),
+                        m_insertionSet.insertConstantForUse(
+                            m_nodeIndex, origin, jsNumber(result ? result.end : 0), UntypedUse));
 
-                    UniquedStringImpl* indexUID = vm().propertyNames->index.impl();
-                    UniquedStringImpl* inputUID = vm().propertyNames->input.impl();
-                    unsigned indexIndex = m_graph.identifiers().ensure(indexUID);
-                    unsigned inputIndex = m_graph.identifiers().ensure(inputUID);
+                    origin = origin.withInvalidExit();
+                }
 
+                if (result) {
                     unsigned firstChild = m_graph.m_varArgChildren.size();
                     m_graph.m_varArgChildren.append(
                         m_insertionSet.insertConstantForUse(
-                            m_nodeIndex, origin, structure, KnownCellUse));
-                    ObjectMaterializationData* data = m_graph.m_objectMaterializationData.add();
-            
+                            m_nodeIndex, origin, constructorFrozenValue, KnownCellUse));
                     m_graph.m_varArgChildren.append(
                         m_insertionSet.insertConstantForUse(
-                            m_nodeIndex, origin, jsNumber(publicLength), KnownInt32Use));
-                    data->m_properties.append(PublicLengthPLoc);
-            
+                            m_nodeIndex, origin, regExpFrozenValue, KnownCellUse));
+                    m_graph.m_varArgChildren.append(Edge(stringNode, KnownCellUse));
                     m_graph.m_varArgChildren.append(
                         m_insertionSet.insertConstantForUse(
-                            m_nodeIndex, origin, jsNumber(vectorLength), KnownInt32Use));
-                    data->m_properties.append(VectorLengthPLoc);
-
+                            m_nodeIndex, origin, jsNumber(result.start), KnownInt32Use));
                     m_graph.m_varArgChildren.append(
                         m_insertionSet.insertConstantForUse(
-                            m_nodeIndex, origin, jsNumber(result.start), UntypedUse));
-                    data->m_properties.append(
-                        PromotedLocationDescriptor(NamedPropertyPLoc, indexIndex));
-
-                    m_graph.m_varArgChildren.append(Edge(stringNode, UntypedUse));
-                    data->m_properties.append(
-                        PromotedLocationDescriptor(NamedPropertyPLoc, inputIndex));
-
-                    auto materializeString = [&] (const String& string) -> Node* {
-                        if (string.isNull())
-                            return nullptr;
-                        if (string.isEmpty()) {
-                            return m_insertionSet.insertConstant(
-                                m_nodeIndex, origin, vm().smallStrings.emptyString());
-                        }
-                        LazyJSValue value = LazyJSValue::newString(m_graph, string);
-                        return m_insertionSet.insertNode(
-                            m_nodeIndex, SpecNone, LazyJSConstant, origin,
-                            OpInfo(m_graph.m_lazyJSValues.add(value)));
-                    };
-
-                    for (unsigned i = 0; i < resultArray.size(); ++i) {
-                        if (Node* node = materializeString(resultArray[i])) {
-                            m_graph.m_varArgChildren.append(Edge(node, UntypedUse));
-                            data->m_properties.append(
-                                PromotedLocationDescriptor(IndexedPropertyPLoc, i));
-                        }
-                    }
-            
-                    Node* resultNode = m_insertionSet.insertNode(
-                        m_nodeIndex, SpecArray, Node::VarArg, MaterializeNewObject, origin,
-                        OpInfo(structureSet), OpInfo(data), firstChild,
-                        m_graph.m_varArgChildren.size() - firstChild);
-                
-                    m_node->convertToIdentityOn(resultNode);
-                } else
-                    m_graph.convertToConstant(m_node, jsNull());
-            } else
-                m_graph.convertToConstant(m_node, jsBoolean(!!result));
+                            m_nodeIndex, origin, jsNumber(result.end), KnownInt32Use));
+                    m_insertionSet.insertNode(
+                        m_nodeIndex, SpecNone, Node::VarArg, RecordRegExpCachedResult, origin,
+                        OpInfo(), OpInfo(), firstChild, m_graph.m_varArgChildren.size() - firstChild);
 
-            // Whether it's Exec or Test, we need to tell the constructor and RegExpObject what's up.
-            // Because SetRegExpObjectLastIndex may exit and it clobbers exit state, we do that
-            // first.
-            
-            if (regExp->globalOrSticky()) {
-                m_insertionSet.insertNode(
-                    m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
-                    Edge(regExpObjectNode, RegExpObjectUse),
-                    m_insertionSet.insertConstantForUse(
-                        m_nodeIndex, origin, jsNumber(result ? result.end : 0), UntypedUse));
-                
-                origin = origin.withInvalidExit();
-            }
+                    origin = origin.withInvalidExit();
+                }
 
-            if (result) {
-                unsigned firstChild = m_graph.m_varArgChildren.size();
-                m_graph.m_varArgChildren.append(
-                    m_insertionSet.insertConstantForUse(
-                        m_nodeIndex, origin, constructorFrozenValue, KnownCellUse));
-                m_graph.m_varArgChildren.append(
-                    m_insertionSet.insertConstantForUse(
-                        m_nodeIndex, origin, regExpFrozenValue, KnownCellUse));
-                m_graph.m_varArgChildren.append(Edge(stringNode, KnownCellUse));
-                m_graph.m_varArgChildren.append(
-                    m_insertionSet.insertConstantForUse(
-                        m_nodeIndex, origin, jsNumber(result.start), KnownInt32Use));
-                m_graph.m_varArgChildren.append(
-                    m_insertionSet.insertConstantForUse(
-                        m_nodeIndex, origin, jsNumber(result.end), KnownInt32Use));
+                m_node->origin = origin;
+                return true;
+            };
+
+            auto convertToStatic = [&] {
+                if (m_node->op() != RegExpExec)
+                    return false;
+                if (regExp->globalOrSticky())
+                    return false;
+                if (m_node->child3().useKind() != StringUse)
+                    return false;
+                NodeOrigin origin = m_node->origin;
                 m_insertionSet.insertNode(
-                    m_nodeIndex, SpecNone, Node::VarArg, RecordRegExpCachedResult, origin,
-                    OpInfo(), OpInfo(), firstChild, m_graph.m_varArgChildren.size() - firstChild);
+                    m_nodeIndex, SpecNone, Check, origin, m_node->children.justChecks());
+                m_node->convertToRegExpExecNonGlobalOrSticky(m_graph.freeze(regExp));
+                m_changed = true;
+                return true;
+            };
+
+            if (foldToConstant())
+                break;
+
+            if (convertToStatic())
+                break;
 
-                origin = origin.withInvalidExit();
-            }
-            
-            m_node->origin = origin;
             break;
         }
 
@@ -802,6 +851,7 @@ private:
             if (regExp->global()) {
                 m_insertionSet.insertNode(
                     m_nodeIndex, SpecNone, SetRegExpObjectLastIndex, origin,
+                    OpInfo(false),
                     Edge(regExpObjectNode, RegExpObjectUse),
                     m_insertionSet.insertConstantForUse(
                         m_nodeIndex, origin, jsNumber(0), UntypedUse));
index 4459636..7842b3a 100644 (file)
@@ -582,6 +582,7 @@ private:
                 case PhantomNewAsyncFunction:
                 case PhantomNewAsyncGeneratorFunction:
                 case PhantomCreateActivation:
+                case PhantomNewRegexp:
                 case GetMyArgumentByVal:
                 case GetMyArgumentByValOutOfBounds:
                 case PutHint:
@@ -738,6 +739,7 @@ private:
                 case PhantomDirectArguments:
                 case PhantomCreateRest:
                 case PhantomClonedArguments:
+                case PhantomNewRegexp:
                 case MovHint:
                 case Upsilon:
                 case ForwardVarargs:
index 8a2dd3d..02e16a5 100644 (file)
@@ -246,6 +246,7 @@ inline CapabilityLevel canCompile(Node* node)
     case PhantomNewAsyncGeneratorFunction:
     case PhantomNewAsyncFunction:
     case PhantomCreateActivation:
+    case PhantomNewRegexp:
     case PutHint:
     case CheckStructureImmediate:
     case MaterializeNewObject:
@@ -276,6 +277,7 @@ inline CapabilityLevel canCompile(Node* node)
     case CreateRest:
     case GetRestLength:
     case RegExpExec:
+    case RegExpExecNonGlobalOrSticky:
     case RegExpTest:
     case RegExpMatchFast:
     case NewRegexp:
index fd67fd6..0fe58d7 100644 (file)
@@ -1182,6 +1182,9 @@ private:
         case RegExpExec:
             compileRegExpExec();
             break;
+        case RegExpExecNonGlobalOrSticky:
+            compileRegExpExecNonGlobalOrSticky();
+            break;
         case RegExpTest:
             compileRegExpTest();
             break;
@@ -1267,6 +1270,7 @@ private:
         case PhantomNewArrayWithSpread:
         case PhantomNewArrayBuffer:
         case PhantomClonedArguments:
+        case PhantomNewRegexp:
         case PutHint:
         case BottomValue:
         case KillStack:
@@ -10260,6 +10264,15 @@ private:
         setJSValue(result);
     }
 
+    void compileRegExpExecNonGlobalOrSticky()
+    {
+        LValue globalObject = lowCell(m_node->child1());
+        LValue argument = lowString(m_node->child2());
+        LValue result = vmCall(
+            Int64, m_out.operation(operationRegExpExecNonGlobalOrSticky), m_callFrame, globalObject, frozenPointer(m_node->cellOperand()), argument);
+        setJSValue(result);
+    }
+
     void compileRegExpTest()
     {
         LValue globalObject = lowCell(m_node->child1());
@@ -10306,6 +10319,7 @@ private:
     void compileNewRegexp()
     {
         FrozenValue* regexp = m_node->cellOperand();
+        LValue lastIndex = lowJSValue(m_node->child1());
         ASSERT(regexp->cell()->inherits(vm(), RegExp::info()));
         ASSERT(m_node->castOperand<RegExp*>()->isValid());
 
@@ -10317,7 +10331,7 @@ private:
         auto structure = m_graph.registerStructure(m_graph.globalObjectFor(m_node->origin.semantic)->regExpStructure());
         LValue fastResultValue = allocateObject<RegExpObject>(structure, m_out.intPtrZero, m_out.int32Zero, slowCase);
         m_out.storePtr(frozenPointer(regexp), fastResultValue, m_heaps.RegExpObject_regExp);
-        m_out.store64(m_out.constInt64(JSValue::encode(jsNumber(0))), fastResultValue, m_heaps.RegExpObject_lastIndex);
+        m_out.store64(lastIndex, fastResultValue, m_heaps.RegExpObject_lastIndex);
         m_out.store32As8(m_out.constInt32(true), m_out.address(fastResultValue, m_heaps.RegExpObject_lastIndexIsWritable));
         mutatorFence();
         ValueFromBlock fastResult = m_out.anchor(fastResultValue);
@@ -10329,9 +10343,9 @@ private:
         LValue slowResultValue = lazySlowPath(
             [=, &vm] (const Vector<Location>& locations) -> RefPtr<LazySlowPath::Generator> {
                 return createLazyCallGenerator(vm,
-                    operationNewRegexp, locations[0].directGPR(),
-                    CCallHelpers::TrustedImmPtr(regexpCell));
-            });
+                    operationNewRegexpWithLastIndex, locations[0].directGPR(),
+                    CCallHelpers::TrustedImmPtr(regexpCell), locations[1].directGPR());
+            }, lastIndex);
         ValueFromBlock slowResult = m_out.anchor(slowResultValue);
         m_out.jump(continuation);
 
@@ -10398,14 +10412,19 @@ private:
 
     void compileSetRegExpObjectLastIndex()
     {
-        LValue regExp = lowRegExpObject(m_node->child1());
-        LValue value = lowJSValue(m_node->child2());
+        if (!m_node->ignoreLastIndexIsWritable()) {
+            LValue regExp = lowRegExpObject(m_node->child1());
+            LValue value = lowJSValue(m_node->child2());
 
-        speculate(
-            ExoticObjectMode, noValue(), nullptr,
-            m_out.isZero32(m_out.load8ZeroExt32(regExp, m_heaps.RegExpObject_lastIndexIsWritable)));
+            speculate(
+                ExoticObjectMode, noValue(), nullptr,
+                m_out.isZero32(m_out.load8ZeroExt32(regExp, m_heaps.RegExpObject_lastIndexIsWritable)));
+
+            m_out.store64(value, regExp, m_heaps.RegExpObject_lastIndex);
+            return;
+        }
         
-        m_out.store64(value, regExp, m_heaps.RegExpObject_lastIndex);
+        m_out.store64(lowJSValue(m_node->child2()), lowCell(m_node->child1()), m_heaps.RegExpObject_lastIndex);
     }
     
     void compileLogShadowChickenPrologue()
index c258c80..ac5afc4 100644 (file)
@@ -40,6 +40,7 @@
 #include "JSFixedArray.h"
 #include "JSGeneratorFunction.h"
 #include "JSLexicalEnvironment.h"
+#include "RegExpObject.h"
 
 namespace JSC { namespace FTL {
 
@@ -110,6 +111,19 @@ extern "C" void JIT_OPERATION operationPopulateObjectInOSR(
         break;
     }
 
+    case PhantomNewRegexp: {
+        RegExpObject* regExpObject = jsCast<RegExpObject*>(JSValue::decode(*encodedValue));
+
+        for (unsigned i = materialization->properties().size(); i--;) {
+            const ExitPropertyValue& property = materialization->properties()[i];
+            if (property.location().kind() != RegExpObjectLastIndexPLoc)
+                continue;
+
+            regExpObject->setLastIndex(exec, JSValue::decode(values[i]), false /* shouldThrow */);
+            break;
+        }
+        break;
+    }
 
     default:
         RELEASE_ASSERT_NOT_REACHED();
@@ -533,7 +547,21 @@ extern "C" JSCell* JIT_OPERATION operationMaterializeObjectInOSR(
         return result;
     }
 
-        
+    case PhantomNewRegexp: {
+        RegExp* regExp = nullptr;
+        for (unsigned i = materialization->properties().size(); i--;) {
+            const ExitPropertyValue& property = materialization->properties()[i];
+            if (property.location() == PromotedLocationDescriptor(RegExpObjectRegExpPLoc)) {
+                RELEASE_ASSERT(JSValue::decode(values[i]).asCell()->inherits(vm, RegExp::info()));
+                regExp = jsCast<RegExp*>(JSValue::decode(values[i]));
+            }
+        }
+        RELEASE_ASSERT(regExp);
+        CodeBlock* codeBlock = baselineCodeBlockForOriginAndBaselineCodeBlock(materialization->origin(), exec->codeBlock());
+        Structure* structure = codeBlock->globalObject()->regExpStructure();
+        return RegExpObject::create(vm, structure, regExp);
+    }
+
     default:
         RELEASE_ASSERT_NOT_REACHED();
         return nullptr;
index 106cfca..a8c50bc 100644 (file)
@@ -51,6 +51,7 @@ class JSObject;
 class JSScope;
 class JSString;
 class JSValue;
+class RegExp;
 class RegExpObject;
 class Register;
 class Structure;
@@ -103,6 +104,7 @@ typedef char* UnusedPtr;
     Pc: Instruction* i.e. bytecode PC
     Q: int64_t
     R: Register
+    Re: RegExp*
     Reo: RegExpObject*
     S: size_t
     Sprt: SlowPathReturnType
@@ -136,6 +138,7 @@ typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EE)(ExecState*, ExecState*
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EGReoJ)(ExecState*, JSGlobalObject*, RegExpObject*, EncodedJSValue);
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EGReoJss)(ExecState*, JSGlobalObject*, RegExpObject*, JSString*);
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EGJJ)(ExecState*, JSGlobalObject*, EncodedJSValue, EncodedJSValue);
+typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EGReJss)(ExecState*, JSGlobalObject*, RegExp*, JSString*);
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EI)(ExecState*, UniquedStringImpl*);
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EJ)(ExecState*, EncodedJSValue);
 typedef EncodedJSValue (JIT_OPERATION *J_JITOperation_EJZ)(ExecState*, EncodedJSValue, int32_t);
index 1d54397..da79090 100644 (file)
@@ -39,6 +39,13 @@ public:
         return object;
     }
 
+    static RegExpObject* create(VM& vm, Structure* structure, RegExp* regExp, JSValue lastIndex)
+    {
+        auto* object = create(vm, structure, regExp);
+        object->m_lastIndex.set(vm, object, lastIndex);
+        return object;
+    }
+
     void setRegExp(VM& vm, RegExp* r) { m_regExp.set(vm, this, r); }
     RegExp* regExp() const { return m_regExp.get(); }