[DFG][FTL] Efficiently execute number#toString()
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Sep 2017 03:03:03 +0000 (03:03 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 5 Sep 2017 03:03:03 +0000 (03:03 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170007

Reviewed by Keith Miller.

JSTests:

* microbenchmarks/number-to-string-strength-reduction.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix-10.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix-cse.js: Added.
(test):
* microbenchmarks/number-to-string-with-radix.js: Added.
(test):
* stress/number-to-string-strength-reduction.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-10.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-cse.js: Added.
(shouldBe):
(test):
* stress/number-to-string-with-radix-invalid.js: Added.
(shouldThrow):
* stress/number-to-string-with-radix-watchpoint.js: Added.
(shouldBe):
(test):
(i.i.1e3.Number.prototype.toString):
* stress/number-to-string-with-radix.js: Added.
(shouldBe):
(test):

Source/JavaScriptCore:

In JS, the natural way to convert number to string with radix is `number.toString(radix)`.
However, our IC only cares about cells. If the base value is a number, it always goes to the slow path.

While extending our IC for number and boolean, the most meaningful use of this IC is calling `number.toString(radix)`.
So, in this patch, we first add a fast path for this in DFG by using watchpoint. We set up a watchpoint for
Number.prototype.toString. And if this watchpoint is kept alive and GetById(base, "toString")'s base should be
speculated as Number, we emit Number related Checks and convert GetById to Number.prototype.toString constant.
It removes costly GetById slow path, and makes it non-clobbering node (JSConstant).

In addition, we add NumberToStringWithValidRadixConstant node. We have NumberToStringWithRadix node, but it may
throw an error if the valid value is incorrect (for example, number.toString(2000)). So its clobbering rule is
conservatively use read(World)/write(Heap). But in reality, `number.toString` is mostly called with the constant
radix, and we can easily figure out this radix is valid (2 <= radix && radix < 32).
We add a rule to the constant folding phase to convert NumberToStringWithRadix to NumberToStringWithValidRadixConstant.
It ensures that it has valid constant radix. And we relax our clobbering rule for NumberToStringWithValidRadixConstant.

Added microbenchmarks show performance improvement.

                                              baseline                  patched

number-to-string-with-radix-cse           43.8312+-1.3017     ^      7.4930+-0.5105        ^ definitely 5.8496x faster
number-to-string-with-radix-10             7.2775+-0.5225     ^      2.1906+-0.1864        ^ definitely 3.3222x faster
number-to-string-with-radix               39.7378+-1.4921     ^     16.6137+-0.7776        ^ definitely 2.3919x faster
number-to-string-strength-reduction       94.9667+-2.7157     ^      9.3060+-0.7202        ^ definitely 10.2049x faster

* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGGraph.h:
(JSC::DFG::Graph::isWatchingGlobalObjectWatchpoint):
(JSC::DFG::Graph::isWatchingArrayIteratorProtocolWatchpoint):
(JSC::DFG::Graph::isWatchingNumberToStringWatchpoint):
* dfg/DFGNode.h:
(JSC::DFG::Node::convertToNumberToStringWithValidRadixConstant):
(JSC::DFG::Node::hasValidRadixConstant):
(JSC::DFG::Node::validRadixConstant):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructor):
(JSC::DFG::SpeculativeJIT::compileNumberToStringWithValidRadixConstant):
(JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber): Deleted.
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGStrengthReductionPhase.cpp:
(JSC::DFG::StrengthReductionPhase::handleNode):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileNumberToStringWithValidRadixConstant):
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::JSGlobalObject):
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::numberToStringWatchpoint):
(JSC::JSGlobalObject::numberProtoToStringFunction const):
* runtime/NumberPrototype.cpp:
(JSC::NumberPrototype::finishCreation):
(JSC::toStringWithRadixInternal):
(JSC::toStringWithRadix):
(JSC::int32ToStringInternal):
(JSC::numberToStringInternal):
* runtime/NumberPrototype.h:

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

33 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/number-to-string-strength-reduction.js [new file with mode: 0644]
JSTests/microbenchmarks/number-to-string-with-radix-10.js [new file with mode: 0644]
JSTests/microbenchmarks/number-to-string-with-radix-cse.js [new file with mode: 0644]
JSTests/microbenchmarks/number-to-string-with-radix.js [new file with mode: 0644]
JSTests/stress/number-to-string-strength-reduction.js [new file with mode: 0644]
JSTests/stress/number-to-string-with-radix-10.js [new file with mode: 0644]
JSTests/stress/number-to-string-with-radix-cse.js [new file with mode: 0644]
JSTests/stress/number-to-string-with-radix-invalid.js [new file with mode: 0644]
JSTests/stress/number-to-string-with-radix-watchpoint.js [new file with mode: 0644]
JSTests/stress/number-to-string-with-radix.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGGraph.h
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/dfg/DFGStrengthReductionPhase.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/NumberPrototype.cpp
Source/JavaScriptCore/runtime/NumberPrototype.h

index 1ba586a..ee79af3 100644 (file)
@@ -1,3 +1,37 @@
+2017-09-03  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [DFG][FTL] Efficiently execute number#toString()
+        https://bugs.webkit.org/show_bug.cgi?id=170007
+
+        Reviewed by Keith Miller.
+
+        * microbenchmarks/number-to-string-strength-reduction.js: Added.
+        (test):
+        * microbenchmarks/number-to-string-with-radix-10.js: Added.
+        (test):
+        * microbenchmarks/number-to-string-with-radix-cse.js: Added.
+        (test):
+        * microbenchmarks/number-to-string-with-radix.js: Added.
+        (test):
+        * stress/number-to-string-strength-reduction.js: Added.
+        (shouldBe):
+        (test):
+        * stress/number-to-string-with-radix-10.js: Added.
+        (shouldBe):
+        (test):
+        * stress/number-to-string-with-radix-cse.js: Added.
+        (shouldBe):
+        (test):
+        * stress/number-to-string-with-radix-invalid.js: Added.
+        (shouldThrow):
+        * stress/number-to-string-with-radix-watchpoint.js: Added.
+        (shouldBe):
+        (test):
+        (i.i.1e3.Number.prototype.toString):
+        * stress/number-to-string-with-radix.js: Added.
+        (shouldBe):
+        (test):
+
 2017-09-02  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DFG] Relax arity requirement
diff --git a/JSTests/microbenchmarks/number-to-string-strength-reduction.js b/JSTests/microbenchmarks/number-to-string-strength-reduction.js
new file mode 100644 (file)
index 0000000..2e70095
--- /dev/null
@@ -0,0 +1,9 @@
+function test()
+{
+    var target = 42;
+    return target.toString(16);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i)
+    test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix-10.js b/JSTests/microbenchmarks/number-to-string-with-radix-10.js
new file mode 100644 (file)
index 0000000..9c083cb
--- /dev/null
@@ -0,0 +1,10 @@
+function test()
+{
+    for (var i = 0; i < 10; ++i)
+        var result = i.toString(10);
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+    test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix-cse.js b/JSTests/microbenchmarks/number-to-string-with-radix-cse.js
new file mode 100644 (file)
index 0000000..f6bc152
--- /dev/null
@@ -0,0 +1,14 @@
+function test()
+{
+    for (var i = 0; i < 1e2; ++i) {
+        i.toString(16);
+        i.toString(16);
+        i.toString(16);
+        i.toString(16);
+        i.toString(16);
+    }
+}
+noInline(test);
+
+for (var i = 0; i < 1e3; ++i)
+    test();
diff --git a/JSTests/microbenchmarks/number-to-string-with-radix.js b/JSTests/microbenchmarks/number-to-string-with-radix.js
new file mode 100644 (file)
index 0000000..0213020
--- /dev/null
@@ -0,0 +1,16 @@
+function test()
+{
+    for (var i = 0; i < 10; ++i) {
+        var result = '';
+        result += i.toString(2);
+        result += i.toString(4);
+        result += i.toString(8);
+        result += i.toString(16);
+        result += i.toString(32);
+    }
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+    test();
diff --git a/JSTests/stress/number-to-string-strength-reduction.js b/JSTests/stress/number-to-string-strength-reduction.js
new file mode 100644 (file)
index 0000000..6f0993f
--- /dev/null
@@ -0,0 +1,14 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    var target = 42;
+    return target.toString(16);
+}
+noInline(test);
+
+for (var i = 0; i < 1e6; ++i)
+    shouldBe(test(), `2a`);
diff --git a/JSTests/stress/number-to-string-with-radix-10.js b/JSTests/stress/number-to-string-with-radix-10.js
new file mode 100644 (file)
index 0000000..bf8f6a0
--- /dev/null
@@ -0,0 +1,15 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+noInline(shouldBe);
+
+function test()
+{
+    for (var i = 0; i < 10; ++i)
+        shouldBe(i.toString(10), "" + i);
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+    test();
diff --git a/JSTests/stress/number-to-string-with-radix-cse.js b/JSTests/stress/number-to-string-with-radix-cse.js
new file mode 100644 (file)
index 0000000..db78216
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    var result;
+    for (var i = 0; i < 1e2; ++i) {
+        i.toString(16);
+        i.toString(16);
+        i.toString(16);
+        i.toString(16);
+        result = i.toString(16);
+    }
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e3; ++i)
+    shouldBe(test(), `63`);
diff --git a/JSTests/stress/number-to-string-with-radix-invalid.js b/JSTests/stress/number-to-string-with-radix-invalid.js
new file mode 100644 (file)
index 0000000..0690965
--- /dev/null
@@ -0,0 +1,24 @@
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+function test(i, radix)
+{
+    return i.toString(radix);
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i) {
+    shouldThrow(() => test(i, 42), `RangeError: toString() radix argument must be between 2 and 36`);
+}
diff --git a/JSTests/stress/number-to-string-with-radix-watchpoint.js b/JSTests/stress/number-to-string-with-radix-watchpoint.js
new file mode 100644 (file)
index 0000000..7968f58
--- /dev/null
@@ -0,0 +1,27 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    for (var i = 0; i < 10; ++i) {
+        var result = '';
+        result += i.toString(2);
+        result += i.toString(4);
+        result += i.toString(8);
+        result += i.toString(16);
+        result += i.toString(32);
+    }
+    return result;
+}
+noInline(test);
+
+var result = `1001211199`;
+for (var i = 0; i < 1e4; ++i) {
+    if (i === 1e3) {
+        Number.prototype.toString = function (radix) { return "Hello"; }
+        result = `HelloHelloHelloHelloHello`;
+    }
+    shouldBe(test(), result);
+}
diff --git a/JSTests/stress/number-to-string-with-radix.js b/JSTests/stress/number-to-string-with-radix.js
new file mode 100644 (file)
index 0000000..e51a976
--- /dev/null
@@ -0,0 +1,21 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function test()
+{
+    for (var i = 0; i < 10; ++i) {
+        var result = '';
+        result += i.toString(2);
+        result += i.toString(4);
+        result += i.toString(8);
+        result += i.toString(16);
+        result += i.toString(32);
+    }
+    return result;
+}
+noInline(test);
+
+for (var i = 0; i < 1e4; ++i)
+    shouldBe(test(), `1001211199`);
index 637ffac..dd0d378 100644 (file)
@@ -1,3 +1,88 @@
+2017-09-03  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [DFG][FTL] Efficiently execute number#toString()
+        https://bugs.webkit.org/show_bug.cgi?id=170007
+
+        Reviewed by Keith Miller.
+
+        In JS, the natural way to convert number to string with radix is `number.toString(radix)`.
+        However, our IC only cares about cells. If the base value is a number, it always goes to the slow path.
+
+        While extending our IC for number and boolean, the most meaningful use of this IC is calling `number.toString(radix)`.
+        So, in this patch, we first add a fast path for this in DFG by using watchpoint. We set up a watchpoint for
+        Number.prototype.toString. And if this watchpoint is kept alive and GetById(base, "toString")'s base should be
+        speculated as Number, we emit Number related Checks and convert GetById to Number.prototype.toString constant.
+        It removes costly GetById slow path, and makes it non-clobbering node (JSConstant).
+
+        In addition, we add NumberToStringWithValidRadixConstant node. We have NumberToStringWithRadix node, but it may
+        throw an error if the valid value is incorrect (for example, number.toString(2000)). So its clobbering rule is
+        conservatively use read(World)/write(Heap). But in reality, `number.toString` is mostly called with the constant
+        radix, and we can easily figure out this radix is valid (2 <= radix && radix < 32).
+        We add a rule to the constant folding phase to convert NumberToStringWithRadix to NumberToStringWithValidRadixConstant.
+        It ensures that it has valid constant radix. And we relax our clobbering rule for NumberToStringWithValidRadixConstant.
+
+        Added microbenchmarks show performance improvement.
+
+                                                      baseline                  patched
+
+        number-to-string-with-radix-cse           43.8312+-1.3017     ^      7.4930+-0.5105        ^ definitely 5.8496x faster
+        number-to-string-with-radix-10             7.2775+-0.5225     ^      2.1906+-0.1864        ^ definitely 3.3222x faster
+        number-to-string-with-radix               39.7378+-1.4921     ^     16.6137+-0.7776        ^ definitely 2.3919x faster
+        number-to-string-strength-reduction       94.9667+-2.7157     ^      9.3060+-0.7202        ^ definitely 10.2049x faster
+
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGConstantFoldingPhase.cpp:
+        (JSC::DFG::ConstantFoldingPhase::foldConstants):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGGraph.h:
+        (JSC::DFG::Graph::isWatchingGlobalObjectWatchpoint):
+        (JSC::DFG::Graph::isWatchingArrayIteratorProtocolWatchpoint):
+        (JSC::DFG::Graph::isWatchingNumberToStringWatchpoint):
+        * dfg/DFGNode.h:
+        (JSC::DFG::Node::convertToNumberToStringWithValidRadixConstant):
+        (JSC::DFG::Node::hasValidRadixConstant):
+        (JSC::DFG::Node::validRadixConstant):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructor):
+        (JSC::DFG::SpeculativeJIT::compileNumberToStringWithValidRadixConstant):
+        (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber): Deleted.
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGStrengthReductionPhase.cpp:
+        (JSC::DFG::StrengthReductionPhase::handleNode):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileNumberToStringWithValidRadixConstant):
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::JSGlobalObject):
+        (JSC::JSGlobalObject::init):
+        (JSC::JSGlobalObject::visitChildren):
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::numberToStringWatchpoint):
+        (JSC::JSGlobalObject::numberProtoToStringFunction const):
+        * runtime/NumberPrototype.cpp:
+        (JSC::NumberPrototype::finishCreation):
+        (JSC::toStringWithRadixInternal):
+        (JSC::toStringWithRadix):
+        (JSC::int32ToStringInternal):
+        (JSC::numberToStringInternal):
+        * runtime/NumberPrototype.h:
+
 2017-09-04  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [DFG] Consider increasing the number of DFG worklist threads
index a1a216a..9da194b 100644 (file)
@@ -1949,10 +1949,25 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         break;
     }
 
-    case NumberToStringWithRadix:
+    case NumberToStringWithRadix: {
+        JSValue radixValue = forNode(node->child2()).m_value;
+        if (radixValue && radixValue.isInt32()) {
+            int32_t radix = radixValue.asInt32();
+            if (2 <= radix && radix <= 36) {
+                m_state.setFoundConstants(true);
+                forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+                break;
+            }
+        }
         clobberWorld(node->origin.semantic, clobberLimit);
         forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
         break;
+    }
+
+    case NumberToStringWithValidRadixConstant: {
+        forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+        break;
+    }
         
     case NewStringObject: {
         ASSERT(node->structure()->classInfo() == StringObject::info());
index 6691bf9..56c06b3 100644 (file)
@@ -1584,9 +1584,14 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
         return;
 
     case NumberToStringWithRadix:
+        // If the radix is invalid, NumberToStringWithRadix can throw an error.
         read(World);
         write(Heap);
         return;
+
+    case NumberToStringWithValidRadixConstant:
+        def(PureValue(node, node->validRadixConstant()));
+        return;
         
     case LastNodeType:
         RELEASE_ASSERT_NOT_REACHED();
index 19e98e8..5ebe854 100644 (file)
@@ -645,6 +645,23 @@ private:
                 break;
             }
 
+            case NumberToStringWithRadix: {
+                JSValue radixValue = m_state.forNode(node->child2()).m_value;
+                if (radixValue && radixValue.isInt32()) {
+                    int32_t radix = radixValue.asInt32();
+                    if (2 <= radix && radix <= 36) {
+                        if (radix == 10) {
+                            node->setOpAndDefaultFlags(ToString);
+                            node->child2() = Edge();
+                        } else
+                            node->convertToNumberToStringWithValidRadixConstant(radix);
+                        changed = true;
+                        break;
+                    }
+                }
+                break;
+            }
+
             case Check: {
                 alreadyHandled = true;
                 m_interpreter.execute(indexInBlock);
index 6d9d228..25b1ffc 100644 (file)
@@ -178,6 +178,7 @@ bool doesGC(Graph& graph, Node* node)
     case ToString:
     case CallStringConstructor:
     case NumberToStringWithRadix:
+    case NumberToStringWithValidRadixConstant:
     case In:
     case HasOwnProperty:
     case Jump:
index aa0be3c..1a3cbc9 100644 (file)
@@ -1311,14 +1311,13 @@ private:
             // FIXME: This should be done in the ByteCodeParser based on reading the
             // PolymorphicAccess, which will surely tell us that this is a AccessCase::ArrayLength.
             // https://bugs.webkit.org/show_bug.cgi?id=154990
+            auto uid = m_graph.identifiers()[node->identifierNumber()];
             if (node->child1()->shouldSpeculateCellOrOther()
                 && !m_graph.hasExitSite(node->origin.semantic, BadType)
                 && !m_graph.hasExitSite(node->origin.semantic, BadCache)
                 && !m_graph.hasExitSite(node->origin.semantic, BadIndexingType)
                 && !m_graph.hasExitSite(node->origin.semantic, ExoticObjectMode)) {
                 
-                auto uid = m_graph.identifiers()[node->identifierNumber()];
-                
                 if (uid == vm().propertyNames->length.impl()) {
                     attemptToMakeGetArrayLength(node);
                     break;
@@ -1333,6 +1332,30 @@ private:
                 }
             }
 
+            if (node->child1()->shouldSpeculateNumber()) {
+                if (uid == vm().propertyNames->toString.impl()) {
+                    if (m_graph.isWatchingNumberToStringWatchpoint(node)) {
+                        JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic);
+                        if (node->child1()->shouldSpeculateInt32()) {
+                            insertCheck<Int32Use>(node->child1().node());
+                            m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+                            break;
+                        }
+
+                        if (enableInt52() && node->child1()->shouldSpeculateAnyInt()) {
+                            insertCheck<Int52RepUse>(node->child1().node());
+                            m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+                            break;
+                        }
+
+                        ASSERT(node->child1()->shouldSpeculateNumber());
+                        insertCheck<DoubleRepUse>(node->child1().node());
+                        m_graph.convertToConstant(node, m_graph.freeze(globalObject->numberProtoToStringFunction()));
+                        break;
+                    }
+                }
+            }
+
             if (node->child1()->shouldSpeculateCell())
                 fixEdge<CellUse>(node->child1());
             break;
@@ -2022,6 +2045,7 @@ private:
         case PutByValWithThis:
         case GetByValWithThis:
         case CompareEqPtr:
+        case NumberToStringWithValidRadixConstant:
             break;
 #else
         default:
index 6630815..5c39c17 100644 (file)
@@ -730,10 +730,8 @@ public:
         return watchpoints().isWatched(globalObject->havingABadTimeWatchpoint());
     }
 
-    bool isWatchingArrayIteratorProtocolWatchpoint(Node* node)
+    bool isWatchingGlobalObjectWatchpoint(JSGlobalObject* globalObject, InlineWatchpointSet& set)
     {
-        JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
-        InlineWatchpointSet& set = globalObject->arrayIteratorProtocolWatchpoint();
         if (watchpoints().isWatched(set))
             return true;
 
@@ -749,6 +747,20 @@ public:
 
         return false;
     }
+
+    bool isWatchingArrayIteratorProtocolWatchpoint(Node* node)
+    {
+        JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
+        InlineWatchpointSet& set = globalObject->arrayIteratorProtocolWatchpoint();
+        return isWatchingGlobalObjectWatchpoint(globalObject, set);
+    }
+
+    bool isWatchingNumberToStringWatchpoint(Node* node)
+    {
+        JSGlobalObject* globalObject = globalObjectFor(node->origin.semantic);
+        InlineWatchpointSet& set = globalObject->numberToStringWatchpoint();
+        return isWatchingGlobalObjectWatchpoint(globalObject, set);
+    }
     
     Profiler::Compilation* compilation() { return m_plan.compilation.get(); }
     
index be5762b..2e3585d 100644 (file)
@@ -691,6 +691,15 @@ public:
         children.setChild2(Edge());
         m_opInfo = cell;
     }
+
+    void convertToNumberToStringWithValidRadixConstant(int32_t radix)
+    {
+        ASSERT(m_op == NumberToStringWithRadix);
+        ASSERT(2 <= radix && radix <= 36);
+        setOpAndDefaultFlags(NumberToStringWithValidRadixConstant);
+        children.setChild2(Edge());
+        m_opInfo = radix;
+    }
     
     void convertToDirectCall(FrozenValue*);
 
@@ -2583,6 +2592,17 @@ public:
         return m_opInfo.as<BucketOwnerType>();
     }
 
+    bool hasValidRadixConstant()
+    {
+        return op() == NumberToStringWithValidRadixConstant;
+    }
+
+    int32_t validRadixConstant()
+    {
+        ASSERT(hasValidRadixConstant());
+        return m_opInfo.as<int32_t>();
+    }
+
     uint32_t errorType()
     {
         ASSERT(op() == ThrowStaticError);
index e9f076c..a2cb823 100644 (file)
@@ -354,6 +354,7 @@ namespace JSC { namespace DFG {
     macro(CallObjectConstructor, NodeResultJS) \
     macro(CallStringConstructor, NodeResultJS | NodeMustGenerate) \
     macro(NumberToStringWithRadix, NodeResultJS | NodeMustGenerate) \
+    macro(NumberToStringWithValidRadixConstant, NodeResultJS) \
     macro(NewStringObject, NodeResultJS) \
     macro(MakeRope, NodeResultJS) \
     macro(In, NodeResultBoolean | NodeMustGenerate) \
index e55b1f7..1088995 100644 (file)
@@ -912,6 +912,7 @@ private:
         case CallStringConstructor:
         case ToString:
         case NumberToStringWithRadix:
+        case NumberToStringWithValidRadixConstant:
         case MakeRope:
         case StrCat: {
             setPrediction(SpecString);
index ad76b33..eae374b 100644 (file)
@@ -288,6 +288,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case ToString:
     case ToNumber:
     case NumberToStringWithRadix:
+    case NumberToStringWithValidRadixConstant:
     case SetFunctionName:
     case StrCat:
     case CallStringConstructor:
index c202908..db8c036 100644 (file)
@@ -8353,7 +8353,7 @@ void SpeculativeJIT::compileToStringOrCallStringConstructor(Node* node)
     case Int32Use:
     case Int52RepUse:
     case DoubleRepUse:
-        compileToStringOrCallStringConstructorOnNumber(node);
+        compileNumberToStringWithValidRadixConstant(node, 10);
         return;
 
     default:
@@ -8434,11 +8434,16 @@ void SpeculativeJIT::compileToStringOrCallStringConstructor(Node* node)
     }
 }
 
-void SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber(Node* node)
+void SpeculativeJIT::compileNumberToStringWithValidRadixConstant(Node* node)
+{
+    compileNumberToStringWithValidRadixConstant(node, node->validRadixConstant());
+}
+
+void SpeculativeJIT::compileNumberToStringWithValidRadixConstant(Node* node, int32_t radix)
 {
     auto callToString = [&] (auto operation, GPRReg resultGPR, auto valueReg) {
         flushRegisters();
-        callOperation(operation, resultGPR, valueReg, CCallHelpers::TrustedImm32(10));
+        callOperation(operation, resultGPR, valueReg, TrustedImm32(radix));
         m_jit.exceptionCheck();
         cellResult(resultGPR, node);
     };
index f34fd46..328cbb1 100644 (file)
@@ -2766,8 +2766,9 @@ public:
     void emitSwitch(Node*);
     
     void compileToStringOrCallStringConstructor(Node*);
-    void compileToStringOrCallStringConstructorOnNumber(Node*);
     void compileNumberToStringWithRadix(Node*);
+    void compileNumberToStringWithValidRadixConstant(Node*);
+    void compileNumberToStringWithValidRadixConstant(Node*, int32_t radix);
     void compileNewStringObject(Node*);
     
     void compileNewTypedArray(Node*);
index 8465de2..80710ae 100644 (file)
@@ -2843,6 +2843,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case NumberToStringWithValidRadixConstant: {
+        compileNumberToStringWithValidRadixConstant(node);
+        break;
+    }
+
     case GetByValWithThis: {
         JSValueOperand base(this, node->child1());
         JSValueRegs baseRegs = base.jsValueRegs();
index c2fdce9..309c47b 100644 (file)
@@ -5303,6 +5303,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case NumberToStringWithValidRadixConstant: {
+        compileNumberToStringWithValidRadixConstant(node);
+        break;
+    }
+
     case IsObject: {
         JSValueOperand value(this, node->child1());
         GPRTemporary result(this, Reuse, value);
index 65afb4a..60092b6 100644 (file)
@@ -426,6 +426,19 @@ private:
             break;
         }
 
+        case NumberToStringWithValidRadixConstant: {
+            Edge& child1 = m_node->child1();
+            if (child1->hasConstant()) {
+                JSValue value = child1->constant()->value();
+                if (value && value.isNumber()) {
+                    String result = toStringWithRadix(value.asNumber(), m_node->validRadixConstant());
+                    m_node->convertToLazyJSConstant(m_graph, LazyJSValue::newString(m_graph, result));
+                    m_changed = true;
+                }
+            }
+            break;
+        }
+
         case GetArrayLength: {
             if (m_node->arrayMode().type() == Array::Generic
                 || m_node->arrayMode().type() == Array::String) {
index c215a63..d1fa639 100644 (file)
@@ -284,6 +284,7 @@ inline CapabilityLevel canCompile(Node* node)
     case DefineAccessorProperty:
     case ToLowerCase:
     case NumberToStringWithRadix:
+    case NumberToStringWithValidRadixConstant:
     case CheckSubClass:
     case CallDOM:
     case CallDOMGetter:
index e7b2462..12c043e 100644 (file)
@@ -1123,6 +1123,9 @@ private:
         case NumberToStringWithRadix:
             compileNumberToStringWithRadix();
             break;
+        case NumberToStringWithValidRadixConstant:
+            compileNumberToStringWithValidRadixConstant();
+            break;
         case CheckSubClass:
             compileCheckSubClass();
             break;
@@ -10419,6 +10422,23 @@ private:
         }
     }
 
+    void compileNumberToStringWithValidRadixConstant()
+    {
+        switch (m_node->child1().useKind()) {
+        case Int32Use:
+            setJSValue(vmCall(pointerType(), m_out.operation(operationInt32ToStringWithValidRadix), m_callFrame, lowInt32(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+            break;
+        case Int52RepUse:
+            setJSValue(vmCall(pointerType(), m_out.operation(operationInt52ToStringWithValidRadix), m_callFrame, lowStrictInt52(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+            break;
+        case DoubleRepUse:
+            setJSValue(vmCall(pointerType(), m_out.operation(operationDoubleToStringWithValidRadix), m_callFrame, lowDouble(m_node->child1()), m_out.constInt32(m_node->validRadixConstant())));
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+    }
+
     void compileResolveScopeForHoistingFuncDeclInEval()
     {
         UniquedStringImpl* uid = m_graph.identifiers()[m_node->identifierNumber()];
index f916a46..0c3a765 100644 (file)
@@ -325,6 +325,7 @@ JSGlobalObject::JSGlobalObject(VM& vm, Structure* structure, const GlobalObjectM
     , m_mapSetWatchpoint(IsWatched)
     , m_setAddWatchpoint(IsWatched)
     , m_arraySpeciesWatchpoint(ClearWatchpoint)
+    , m_numberToStringWatchpoint(IsWatched)
     , m_templateRegistry(vm)
     , m_runtimeFlags()
     , m_globalObjectMethodTable(globalObjectMethodTable ? globalObjectMethodTable : &s_globalObjectMethodTable)
@@ -1015,6 +1016,13 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
             m_setPrototypeAddWatchpoint = std::make_unique<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>>(vm, condition, m_setAddWatchpoint);
             m_setPrototypeAddWatchpoint->install();
         }
+
+        {
+            ObjectPropertyCondition condition = setupAdaptiveWatchpoint(numberPrototype(), m_vm.propertyNames->toString);
+            m_numberPrototypeToStringWatchpoint = std::make_unique<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>>(vm, condition, m_numberToStringWatchpoint);
+            m_numberPrototypeToStringWatchpoint->install();
+            m_numberProtoToStringFunction.set(vm, this, jsCast<JSFunction*>(numberPrototype()->getDirect(vm, vm.propertyNames->toString)));
+        }
     }
 
     resetPrototype(vm, getPrototypeDirect());
@@ -1264,6 +1272,7 @@ void JSGlobalObject::visitChildren(JSCell* cell, SlotVisitor& visitor)
     thisObject->m_iteratorProtocolFunction.visit(visitor);
     thisObject->m_promiseResolveFunction.visit(visitor);
     visitor.append(thisObject->m_objectProtoValueOfFunction);
+    visitor.append(thisObject->m_numberProtoToStringFunction);
     visitor.append(thisObject->m_newPromiseCapabilityFunction);
     visitor.append(thisObject->m_functionProtoHasInstanceSymbolFunction);
     thisObject->m_throwTypeErrorGetterSetter.visit(visitor);
index 6c29d1a..1fb0e96 100644 (file)
@@ -269,6 +269,7 @@ public:
     LazyProperty<JSGlobalObject, JSFunction> m_iteratorProtocolFunction;
     LazyProperty<JSGlobalObject, JSFunction> m_promiseResolveFunction;
     WriteBarrier<JSFunction> m_objectProtoValueOfFunction;
+    WriteBarrier<JSFunction> m_numberProtoToStringFunction;
     WriteBarrier<JSFunction> m_newPromiseCapabilityFunction;
     WriteBarrier<JSFunction> m_functionProtoHasInstanceSymbolFunction;
     LazyProperty<JSGlobalObject, GetterSetter> m_throwTypeErrorGetterSetter;
@@ -413,6 +414,7 @@ public:
     InlineWatchpointSet& mapSetWatchpoint() { return m_mapSetWatchpoint; }
     InlineWatchpointSet& setAddWatchpoint() { return m_setAddWatchpoint; }
     InlineWatchpointSet& arraySpeciesWatchpoint() { return m_arraySpeciesWatchpoint; }
+    InlineWatchpointSet& numberToStringWatchpoint() { return m_numberToStringWatchpoint; }
     // If this hasn't been invalidated, it means the array iterator protocol
     // is not observable to user code yet.
     InlineWatchpointSet m_arrayIteratorProtocolWatchpoint;
@@ -422,6 +424,7 @@ public:
     InlineWatchpointSet m_mapSetWatchpoint;
     InlineWatchpointSet m_setAddWatchpoint;
     InlineWatchpointSet m_arraySpeciesWatchpoint;
+    InlineWatchpointSet m_numberToStringWatchpoint;
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_arrayPrototypeSymbolIteratorWatchpoint;
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_arrayIteratorPrototypeNext;
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_mapPrototypeSymbolIteratorWatchpoint;
@@ -432,6 +435,7 @@ public:
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_stringIteratorPrototypeNextWatchpoint;
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_mapPrototypeSetWatchpoint;
     std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_setPrototypeAddWatchpoint;
+    std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_numberPrototypeToStringWatchpoint;
 
     bool isArrayPrototypeIteratorProtocolFastAndNonObservable();
     bool isMapPrototypeIteratorProtocolFastAndNonObservable();
@@ -542,6 +546,7 @@ public:
     JSFunction* iteratorProtocolFunction() const { return m_iteratorProtocolFunction.get(this); }
     JSFunction* promiseResolveFunction() const { return m_promiseResolveFunction.get(this); }
     JSFunction* objectProtoValueOfFunction() const { return m_objectProtoValueOfFunction.get(); }
+    JSFunction* numberProtoToStringFunction() const { return m_numberProtoToStringFunction.get(); }
     JSFunction* newPromiseCapabilityFunction() const { return m_newPromiseCapabilityFunction.get(); }
     JSFunction* functionProtoHasInstanceSymbolFunction() const { return m_functionProtoHasInstanceSymbolFunction.get(); }
     JSObject* regExpProtoExecFunction() const { return m_regExpProtoExec.get(); }
index 0a7441b..75fcee3 100644 (file)
@@ -58,7 +58,6 @@ const ClassInfo NumberPrototype::s_info = { "Number", &NumberObject::s_info, &nu
 
 /* Source for NumberPrototype.lut.h
 @begin numberPrototypeTable
-  toString          numberProtoFuncToString         DontEnum|Function 1 NumberPrototypeToStringIntrinsic
   toLocaleString    numberProtoFuncToLocaleString   DontEnum|Function 0
   valueOf           numberProtoFuncValueOf          DontEnum|Function 0
   toFixed           numberProtoFuncToFixed          DontEnum|Function 1
@@ -79,10 +78,9 @@ void NumberPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject)
     Base::finishCreation(vm);
     setInternalValue(vm, jsNumber(0));
 
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->toString, numberProtoFuncToString, DontEnum, 1, NumberPrototypeToStringIntrinsic);
 #if ENABLE(INTL)
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("toLocaleString", numberPrototypeToLocaleStringCodeGenerator, DontEnum);
-#else
-    UNUSED_PARAM(globalObject);
 #endif // ENABLE(INTL)
 
     ASSERT(inherits(vm, info()));
@@ -165,7 +163,7 @@ static inline char* int52ToStringWithRadix(char* startOfResultString, int64_t in
     return startOfResultString;
 }
 
-static char* toStringWithRadix(RadixBuffer& buffer, double originalNumber, unsigned radix)
+static char* toStringWithRadixInternal(RadixBuffer& buffer, double originalNumber, unsigned radix)
 {
     ASSERT(std::isfinite(originalNumber));
     ASSERT(radix >= 2 && radix <= 36);
@@ -359,7 +357,7 @@ static char* toStringWithRadix(RadixBuffer& buffer, double originalNumber, unsig
     return startOfResultString;
 }
 
-static String toStringWithRadix(int32_t number, unsigned radix)
+static String toStringWithRadixInternal(int32_t number, unsigned radix)
 {
     LChar buf[1 + 32]; // Worst case is radix == 2, which gives us 32 digits + sign.
     LChar* end = std::end(buf);
@@ -384,6 +382,21 @@ static String toStringWithRadix(int32_t number, unsigned radix)
     return String(p, static_cast<unsigned>(end - p));
 }
 
+String toStringWithRadix(double doubleValue, int32_t radix)
+{
+    ASSERT(2 <= radix && radix <= 36);
+
+    int32_t integerValue = static_cast<int32_t>(doubleValue);
+    if (integerValue == doubleValue)
+        return toStringWithRadixInternal(integerValue, radix);
+
+    if (radix == 10 || !std::isfinite(doubleValue))
+        return String::numberToStringECMAScript(doubleValue);
+
+    RadixBuffer buffer;
+    return toStringWithRadixInternal(buffer, doubleValue, radix);
+}
+
 // toExponential converts a number to a string, always formatting as an exponential.
 // This method takes an optional argument specifying a number of *decimal places*
 // to round the significand to (or, put another way, this method optionally rounds
@@ -520,7 +533,7 @@ static ALWAYS_INLINE JSString* int32ToStringInternal(VM& vm, int32_t value, int3
     if (radix == 10)
         return jsNontrivialString(&vm, vm.numericStrings.add(value));
 
-    return jsNontrivialString(&vm, toStringWithRadix(value, radix));
+    return jsNontrivialString(&vm, toStringWithRadixInternal(value, radix));
 
 }
 
@@ -539,7 +552,7 @@ static ALWAYS_INLINE JSString* numberToStringInternal(VM& vm, double doubleValue
         return jsNontrivialString(&vm, String::numberToStringECMAScript(doubleValue));
 
     RadixBuffer buffer;
-    return jsString(&vm, toStringWithRadix(buffer, doubleValue, radix));
+    return jsString(&vm, toStringWithRadixInternal(buffer, doubleValue, radix));
 }
 
 JSString* int32ToString(VM& vm, int32_t value, int32_t radix)
index 3c069cb..039b0d5 100644 (file)
@@ -54,5 +54,6 @@ EncodedJSValue JSC_HOST_CALL numberProtoFuncValueOf(ExecState*);
 JSString* int32ToString(VM&, int32_t value, int32_t radix);
 JSString* int52ToString(VM&, int64_t value, int32_t radix);
 JSString* numberToString(VM&, double value, int32_t radix);
+String toStringWithRadix(double doubleValue, int32_t radix);
 
 } // namespace JSC