[JSC] Optimize Number.prototype.toString on Int32 / Int52 / Double
authorutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Mar 2017 11:31:43 +0000 (11:31 +0000)
committerutatane.tea@gmail.com <utatane.tea@gmail.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Mar 2017 11:31:43 +0000 (11:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=167454

Reviewed by Saam Barati.

JSTests:

* stress/number-to-string-abstract-operation.js: Added.
(shouldBe):
(int32ToString):
(shouldBe.int32ToString.new.Number.int52ToString):
(shouldBe.int32ToString.new.Number):
(shouldBe.doubleToString):
* stress/number-to-string-radix.js: Added.
(shouldBe):
(int32ToString):
(shouldBe.int32ToString.new.Number.int52ToString):
(shouldBe.int32ToString.new.Number):
(shouldBe.doubleToString):
* stress/number-to-string.js: Added.
(shouldBe):
(int32ToString):
(shouldBe.int32ToString.new.Number.int52ToString):
(shouldBe.int32ToString.new.Number):
(shouldBe.doubleToString):

Source/JavaScriptCore:

This patch improves Number.toString(radix) performance
by introducing NumberToStringWithRadix DFG node. It directly
calls the operation and it always returns String.

                                               baseline                  patched

    stanford-crypto-sha256-iterative        45.130+-0.928             44.032+-1.184           might be 1.0250x faster

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

28 files changed:
JSTests/ChangeLog
JSTests/stress/number-to-string-abstract-operation.js [new file with mode: 0644]
JSTests/stress/number-to-string-radix.js [new file with mode: 0644]
JSTests/stress/number-to-string.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.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/jit/CCallHelpers.h
Source/JavaScriptCore/jit/JITOperations.h
Source/JavaScriptCore/runtime/Intrinsic.h
Source/JavaScriptCore/runtime/NumberPrototype.cpp
Source/JavaScriptCore/runtime/NumberPrototype.h
Source/JavaScriptCore/runtime/StringPrototype.cpp

index 4420f89..aaccffb 100644 (file)
@@ -1,3 +1,29 @@
+2017-03-21  Yusuke Suzuki  <utatane.tea@gmail.com>
+
+        [JSC] Optimize Number.prototype.toString on Int32 / Int52 / Double
+        https://bugs.webkit.org/show_bug.cgi?id=167454
+
+        Reviewed by Saam Barati.
+
+        * stress/number-to-string-abstract-operation.js: Added.
+        (shouldBe):
+        (int32ToString):
+        (shouldBe.int32ToString.new.Number.int52ToString):
+        (shouldBe.int32ToString.new.Number):
+        (shouldBe.doubleToString):
+        * stress/number-to-string-radix.js: Added.
+        (shouldBe):
+        (int32ToString):
+        (shouldBe.int32ToString.new.Number.int52ToString):
+        (shouldBe.int32ToString.new.Number):
+        (shouldBe.doubleToString):
+        * stress/number-to-string.js: Added.
+        (shouldBe):
+        (int32ToString):
+        (shouldBe.int32ToString.new.Number.int52ToString):
+        (shouldBe.int32ToString.new.Number):
+        (shouldBe.doubleToString):
+
 2017-03-19  Chris Dumez  <cdumez@apple.com>
 
         `const location = "foo"` throws in a worker
diff --git a/JSTests/stress/number-to-string-abstract-operation.js b/JSTests/stress/number-to-string-abstract-operation.js
new file mode 100644 (file)
index 0000000..e1a9d5d
--- /dev/null
@@ -0,0 +1,80 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error(`bad value: expected:(${expected}),actual:(${actual})`);
+}
+
+function expected(num, radix)
+{
+    let characters = "0123456789abcdefghijklmnopqrstuvwxyz";
+    let result = "";
+    let negative = false;
+    if (num < 0) {
+        negative = true;
+        num = -num;
+    }
+
+    do {
+        const index = num % radix;
+        result = characters[index] + result;
+        num = (num - index) / radix;
+    } while (num);
+
+    if (negative)
+        return '-' + result;
+    return result;
+}
+
+{
+    function int32ToString(num)
+    {
+        return `${num}`;
+    }
+    noInline(int32ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int32ToString(i), expected(i, 10));
+        shouldBe(int32ToString(-i), expected(-i, 10));
+    }
+
+    shouldBe(int32ToString(0xffffffffff), expected(0xffffffffff, 10));
+    shouldBe(int32ToString(0.1), `0.1`);
+    shouldBe(int32ToString(-0.1), `-0.1`);
+    shouldBe(int32ToString(new Number(0xff)), `255`);
+}
+
+{
+    function int52ToString(num)
+    {
+        return `${fiatInt52(num)}`;
+    }
+    noInline(int52ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int52ToString(0xffffffff + i), expected(0xffffffff + i, 10));
+        shouldBe(int52ToString(-(0xffffffff + i)), expected(-(0xffffffff + i), 10));
+    }
+
+    shouldBe(int52ToString(0xff), `255`);
+    shouldBe(int52ToString(0.1), `0.1`);
+    shouldBe(int52ToString(-0.1), `-0.1`);
+    shouldBe(int52ToString(new Number(0xff)), `255`);
+}
+
+{
+    function doubleToString(num)
+    {
+        return `${num}`;
+    }
+    noInline(doubleToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(doubleToString(1.01), `1.01`);
+        shouldBe(doubleToString(-1.01), `-1.01`);
+    }
+
+    shouldBe(doubleToString(0xff), `255`);
+    shouldBe(doubleToString(0.1), `0.1`);
+    shouldBe(doubleToString(-0.1), `-0.1`);
+    shouldBe(doubleToString(new Number(0xff)), `255`);
+}
diff --git a/JSTests/stress/number-to-string-radix.js b/JSTests/stress/number-to-string-radix.js
new file mode 100644 (file)
index 0000000..6dd3a84
--- /dev/null
@@ -0,0 +1,80 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error(`bad value: expected:(${expected}),actual:(${actual})`);
+}
+
+function expected(num, radix)
+{
+    let characters = "0123456789abcdefghijklmnopqrstuvwxyz";
+    let result = "";
+    let negative = false;
+    if (num < 0) {
+        negative = true;
+        num = -num;
+    }
+
+    do {
+        const index = num % radix;
+        result = characters[index] + result;
+        num = (num - index) / radix;
+    } while (num);
+
+    if (negative)
+        return '-' + result;
+    return result;
+}
+
+{
+    function int32ToString(num, radix)
+    {
+        return num.toString(radix);
+    }
+    noInline(int32ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int32ToString(i, 16), expected(i, 16));
+        shouldBe(int32ToString(-i, 16), expected(-i, 16));
+    }
+
+    shouldBe(int32ToString(0xffffffffff, 16), expected(0xffffffffff, 16));
+    shouldBe(int32ToString(0.1, 16), `0.1999999999999a`);
+    shouldBe(int32ToString(-0.1, 16), `-0.1999999999999a`);
+    shouldBe(int32ToString(new Number(0xff), 16), `ff`);
+}
+
+{
+    function int52ToString(num, radix)
+    {
+        return fiatInt52(num).toString(radix);
+    }
+    noInline(int52ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int52ToString(0xffffffff + i, 16), expected(0xffffffff + i, 16));
+        shouldBe(int52ToString(-(0xffffffff + i), 16), expected(-(0xffffffff + i), 16));
+    }
+
+    shouldBe(int52ToString(0xff, 16), `ff`);
+    shouldBe(int52ToString(0.1, 16), `0.1999999999999a`);
+    shouldBe(int52ToString(-0.1, 16), `-0.1999999999999a`);
+    shouldBe(int52ToString(new Number(0xff), 16), `ff`);
+}
+
+{
+    function doubleToString(num, radix)
+    {
+        return num.toString(radix);
+    }
+    noInline(doubleToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(doubleToString(1.01, 16), `1.028f5c28f5c29`);
+        shouldBe(doubleToString(-1.01, 16), `-1.028f5c28f5c29`);
+    }
+
+    shouldBe(doubleToString(0xff, 16), `ff`);
+    shouldBe(doubleToString(0.1, 16), `0.1999999999999a`);
+    shouldBe(doubleToString(-0.1, 16), `-0.1999999999999a`);
+    shouldBe(doubleToString(new Number(0xff), 16), `ff`);
+}
diff --git a/JSTests/stress/number-to-string.js b/JSTests/stress/number-to-string.js
new file mode 100644 (file)
index 0000000..ae0c2fa
--- /dev/null
@@ -0,0 +1,80 @@
+function shouldBe(actual, expected)
+{
+    if (actual !== expected)
+        throw new Error(`bad value: expected:(${expected}),actual:(${actual})`);
+}
+
+function expected(num, radix)
+{
+    let characters = "0123456789abcdefghijklmnopqrstuvwxyz";
+    let result = "";
+    let negative = false;
+    if (num < 0) {
+        negative = true;
+        num = -num;
+    }
+
+    do {
+        const index = num % radix;
+        result = characters[index] + result;
+        num = (num - index) / radix;
+    } while (num);
+
+    if (negative)
+        return '-' + result;
+    return result;
+}
+
+{
+    function int32ToString(num)
+    {
+        return num.toString(16);
+    }
+    noInline(int32ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int32ToString(i), expected(i, 16));
+        shouldBe(int32ToString(-i), expected(-i, 16));
+    }
+
+    shouldBe(int32ToString(0xffffffffff), expected(0xffffffffff, 16));
+    shouldBe(int32ToString(0.1), `0.1999999999999a`);
+    shouldBe(int32ToString(-0.1), `-0.1999999999999a`);
+    shouldBe(int32ToString(new Number(0xff)), `ff`);
+}
+
+{
+    function int52ToString(num)
+    {
+        return fiatInt52(num).toString(16);
+    }
+    noInline(int52ToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(int52ToString(0xffffffff + i), expected(0xffffffff + i, 16));
+        shouldBe(int52ToString(-(0xffffffff + i)), expected(-(0xffffffff + i), 16));
+    }
+
+    shouldBe(int52ToString(0xff), `ff`);
+    shouldBe(int52ToString(0.1), `0.1999999999999a`);
+    shouldBe(int52ToString(-0.1), `-0.1999999999999a`);
+    shouldBe(int52ToString(new Number(0xff)), `ff`);
+}
+
+{
+    function doubleToString(num)
+    {
+        return num.toString(16);
+    }
+    noInline(doubleToString);
+
+    for (var i = 0; i < 1e3; ++i) {
+        shouldBe(doubleToString(1.01), `1.028f5c28f5c29`);
+        shouldBe(doubleToString(-1.01), `-1.028f5c28f5c29`);
+    }
+
+    shouldBe(doubleToString(0xff), `ff`);
+    shouldBe(doubleToString(0.1), `0.1999999999999a`);
+    shouldBe(doubleToString(-0.1), `-0.1999999999999a`);
+    shouldBe(doubleToString(new Number(0xff)), `ff`);
+}
index 78a7995..106260e 100644 (file)
@@ -1,5 +1,20 @@
 2017-03-21  Yusuke Suzuki  <utatane.tea@gmail.com>
 
+        [JSC] Optimize Number.prototype.toString on Int32 / Int52 / Double
+        https://bugs.webkit.org/show_bug.cgi?id=167454
+
+        Reviewed by Saam Barati.
+
+        This patch improves Number.toString(radix) performance
+        by introducing NumberToStringWithRadix DFG node. It directly
+        calls the operation and it always returns String.
+
+                                                       baseline                  patched
+
+            stanford-crypto-sha256-iterative        45.130+-0.928             44.032+-1.184           might be 1.0250x faster
+
+2017-03-21  Yusuke Suzuki  <utatane.tea@gmail.com>
+
         [JSC] Add JSPromiseDeferred::reject(ExecState*, Exception*) interface
         https://bugs.webkit.org/show_bug.cgi?id=169908
 
         * assembler/MacroAssemblerMIPS.h:
         (JSC::MacroAssemblerMIPS::branchPtr): Added.
 
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsicCall):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        (JSC::DFG::FixupPhase::fixupToStringOrCallStringConstructor):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructor):
+        (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber):
+        (JSC::DFG::SpeculativeJIT::compileNumberToStringWithRadix):
+        (JSC::DFG::SpeculativeJIT::compileToStringOrCallStringConstructorOnCell): Deleted.
+        * 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):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileToStringOrCallStringConstructor):
+        (JSC::FTL::DFG::LowerDFGToB3::compileNumberToStringWithRadix):
+        * jit/CCallHelpers.h:
+        (JSC::CCallHelpers::setupArgumentsWithExecState):
+        * jit/JITOperations.h:
+        * runtime/Intrinsic.h:
+        * runtime/NumberPrototype.cpp:
+        (JSC::int52ToStringWithRadix):
+        (JSC::int32ToStringInternal):
+        (JSC::numberToStringInternal):
+        (JSC::int32ToString):
+        (JSC::int52ToString):
+        (JSC::numberToString):
+        (JSC::numberProtoFuncToString):
+        (JSC::integerValueToString): Deleted.
+        * runtime/NumberPrototype.h:
+        * runtime/StringPrototype.cpp:
+        (JSC::StringPrototype::finishCreation):
+
 2017-03-20  Filip Pizlo  <fpizlo@apple.com>
 
         Graph coloring should use coalescable moves when spilling
index fbfe166..b549eca 100644 (file)
@@ -1858,6 +1858,9 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
                 m_graph.registerStructure(m_graph.globalObjectFor(node->origin.semantic)->stringObjectStructure()));
             break;
         case StringOrStringObjectUse:
+        case Int32Use:
+        case Int52RepUse:
+        case DoubleRepUse:
         case NotCellUse:
             break;
         case CellUse:
@@ -1871,6 +1874,11 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         forNode(node).set(m_graph, m_vm.stringStructure.get());
         break;
     }
+
+    case NumberToStringWithRadix:
+        clobberWorld(node->origin.semantic, clobberLimit);
+        forNode(node).set(m_graph, m_graph.m_vm.stringStructure.get());
+        break;
         
     case NewStringObject: {
         ASSERT(node->structure()->classInfo() == StringObject::info());
index 31e0e0b..3c49c91 100644 (file)
@@ -2711,7 +2711,7 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
         return true;
     }
 
-    case ToLowerCaseIntrinsic: {
+    case StringPrototypeToLowerCaseIntrinsic: {
         if (argumentCountIncludingThis != 1)
             return false;
 
@@ -2725,6 +2725,26 @@ bool ByteCodeParser::handleIntrinsicCall(Node* callee, int resultOperand, Intrin
         return true;
     }
 
+    case NumberPrototypeToStringIntrinsic: {
+        if (argumentCountIncludingThis != 1 && argumentCountIncludingThis != 2)
+            return false;
+
+        if (m_inlineStackTop->m_exitProfile.hasExitSite(m_currentIndex, BadType))
+            return false;
+
+        insertChecks();
+        Node* thisNumber = get(virtualRegisterForArgument(0, registerOffset));
+        if (argumentCountIncludingThis == 1) {
+            Node* result = addToGraph(ToString, thisNumber);
+            set(VirtualRegister(resultOperand), result);
+        } else {
+            Node* radix = get(virtualRegisterForArgument(1, registerOffset));
+            Node* result = addToGraph(NumberToStringWithRadix, thisNumber, radix);
+            set(VirtualRegister(resultOperand), result);
+        }
+        return true;
+    }
+
     default:
         return false;
     }
index d2e372e..8b30fc0 100644 (file)
@@ -1409,6 +1409,9 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
             write(Heap);
             return;
 
+        case Int32Use:
+        case Int52RepUse:
+        case DoubleRepUse:
         case NotCellUse:
             def(PureValue(node));
             return;
@@ -1456,6 +1459,11 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case ToLowerCase:
         def(PureValue(node));
         return;
+
+    case NumberToStringWithRadix:
+        read(World);
+        write(Heap);
+        return;
         
     case LastNodeType:
         RELEASE_ASSERT_NOT_REACHED();
index c7aae3d..b96a362 100644 (file)
@@ -178,6 +178,7 @@ bool doesGC(Graph& graph, Node* node)
     case ToNumber:
     case ToString:
     case CallStringConstructor:
+    case NumberToStringWithRadix:
     case In:
     case HasOwnProperty:
     case Jump:
index 9904e73..b2f2a80 100644 (file)
@@ -1761,6 +1761,17 @@ private:
             break;
         }
 
+        case NumberToStringWithRadix: {
+            if (node->child1()->shouldSpeculateInt32())
+                fixEdge<Int32Use>(node->child1());
+            else if (enableInt52() && node->child1()->shouldSpeculateAnyInt())
+                fixEdge<Int52RepUse>(node->child1());
+            else
+                fixEdge<DoubleRepUse>(node->child1());
+            fixEdge<Int32Use>(node->child2());
+            break;
+        }
+
         case DefineAccessorProperty: {
             fixEdge<CellUse>(m_graph.varArgChild(node, 0));
             Edge& propertyEdge = m_graph.varArgChild(node, 1);
@@ -2236,6 +2247,24 @@ private:
             return;
         }
 
+        if (node->child1()->shouldSpeculateInt32()) {
+            fixEdge<Int32Use>(node->child1());
+            node->clearFlags(NodeMustGenerate);
+            return;
+        }
+
+        if (enableInt52() && node->child1()->shouldSpeculateAnyInt()) {
+            fixEdge<Int52RepUse>(node->child1());
+            node->clearFlags(NodeMustGenerate);
+            return;
+        }
+
+        if (node->child1()->shouldSpeculateNumber()) {
+            fixEdge<DoubleRepUse>(node->child1());
+            node->clearFlags(NodeMustGenerate);
+            return;
+        }
+
         // ToString(Symbol) throws an error. So if the child1 can include Symbols,
         // we need to care about it in the clobberize. In the following case,
         // since NotCellUse edge filter is used and this edge filters Symbols,
index 1ce230b..6c62ce6 100644 (file)
@@ -335,6 +335,7 @@ namespace JSC { namespace DFG {
     macro(ToNumber, NodeResultJS | NodeMustGenerate) \
     macro(CallObjectConstructor, NodeResultJS) \
     macro(CallStringConstructor, NodeResultJS | NodeMustGenerate) \
+    macro(NumberToStringWithRadix, NodeResultJS | NodeMustGenerate) \
     macro(NewStringObject, NodeResultJS) \
     macro(MakeRope, NodeResultJS) \
     macro(In, NodeResultBoolean | NodeMustGenerate) \
index 35ae636..5c6aa43 100644 (file)
@@ -1644,6 +1644,75 @@ JSString* JIT_OPERATION operationToLowerCase(ExecState* exec, JSString* string,
     return jsString(exec, lowercasedString);
 }
 
+char* JIT_OPERATION operationInt32ToString(ExecState* exec, int32_t value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (radix < 2 || radix > 36) {
+        throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("toString() radix argument must be between 2 and 36")));
+        return nullptr;
+    }
+
+    return reinterpret_cast<char*>(int32ToString(vm, value, radix));
+}
+
+char* JIT_OPERATION operationInt52ToString(ExecState* exec, int64_t value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (radix < 2 || radix > 36) {
+        throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("toString() radix argument must be between 2 and 36")));
+        return nullptr;
+    }
+
+    return reinterpret_cast<char*>(int52ToString(vm, value, radix));
+}
+
+char* JIT_OPERATION operationDoubleToString(ExecState* exec, double value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (radix < 2 || radix > 36) {
+        throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("toString() radix argument must be between 2 and 36")));
+        return nullptr;
+    }
+
+    return reinterpret_cast<char*>(numberToString(vm, value, radix));
+}
+
+char* JIT_OPERATION operationInt32ToStringWithValidRadix(ExecState* exec, int32_t value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    return reinterpret_cast<char*>(int32ToString(vm, value, radix));
+}
+
+char* JIT_OPERATION operationInt52ToStringWithValidRadix(ExecState* exec, int64_t value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    return reinterpret_cast<char*>(int52ToString(vm, value, radix));
+}
+
+char* JIT_OPERATION operationDoubleToStringWithValidRadix(ExecState* exec, double value, int32_t radix)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    return reinterpret_cast<char*>(numberToString(vm, value, radix));
+}
+
 JSString* JIT_OPERATION operationSingleCharacterString(ExecState* exec, int32_t character)
 {
     VM& vm = exec->vm();
index 852195c..4386a66 100644 (file)
@@ -159,6 +159,13 @@ JSString* JIT_OPERATION operationSingleCharacterString(ExecState*, int32_t);
 
 JSString* JIT_OPERATION operationToLowerCase(ExecState*, JSString*, uint32_t);
 
+char* JIT_OPERATION operationInt32ToString(ExecState*, int32_t, int32_t);
+char* JIT_OPERATION operationInt52ToString(ExecState*, int64_t, int32_t);
+char* JIT_OPERATION operationDoubleToString(ExecState*, double, int32_t);
+char* JIT_OPERATION operationInt32ToStringWithValidRadix(ExecState*, int32_t, int32_t);
+char* JIT_OPERATION operationInt52ToStringWithValidRadix(ExecState*, int64_t, int32_t);
+char* JIT_OPERATION operationDoubleToStringWithValidRadix(ExecState*, double, int32_t);
+
 int32_t JIT_OPERATION operationMapHash(ExecState*, EncodedJSValue input);
 JSCell* JIT_OPERATION operationJSMapFindBucket(ExecState*, JSCell*, EncodedJSValue, int32_t);
 JSCell* JIT_OPERATION operationJSSetFindBucket(ExecState*, JSCell*, EncodedJSValue, int32_t);
index 82566df..ba334c6 100644 (file)
@@ -897,6 +897,7 @@ private:
         case StringCharAt:
         case CallStringConstructor:
         case ToString:
+        case NumberToStringWithRadix:
         case MakeRope:
         case StrCat: {
             setPrediction(SpecString);
index b97ebca..33a3947 100644 (file)
@@ -288,6 +288,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case ToPrimitive:
     case ToString:
     case ToNumber:
+    case NumberToStringWithRadix:
     case SetFunctionName:
     case StrCat:
     case CallStringConstructor:
index f3273fc..3727940 100644 (file)
@@ -7923,9 +7923,10 @@ GPRReg SpeculativeJIT::temporaryRegisterForPutByVal(GPRTemporary& temporary, Arr
     return temporary.gpr();
 }
 
-void SpeculativeJIT::compileToStringOrCallStringConstructorOnCell(Node* node)
+void SpeculativeJIT::compileToStringOrCallStringConstructor(Node* node)
 {
-    if (node->child1().useKind() == NotCellUse) {
+    switch (node->child1().useKind()) {
+    case NotCellUse: {
         JSValueOperand op1(this, node->child1(), ManualOperandSpeculation);
         JSValueRegs op1Regs = op1.jsValueRegs();
 
@@ -7947,7 +7948,7 @@ void SpeculativeJIT::compileToStringOrCallStringConstructorOnCell(Node* node)
         return;
     }
 
-    if (node->child1().useKind() == UntypedUse) {
+    case UntypedUse: {
         JSValueOperand op1(this, node->child1());
         JSValueRegs op1Regs = op1.jsValueRegs();
         GPRReg op1PayloadGPR = op1Regs.payloadGPR();
@@ -7979,6 +7980,15 @@ void SpeculativeJIT::compileToStringOrCallStringConstructorOnCell(Node* node)
         return;
     }
 
+    case Int32Use:
+    case Int52RepUse:
+    case DoubleRepUse:
+        compileToStringOrCallStringConstructorOnNumber(node);
+        return;
+
+    default:
+        break;
+    }
 
     SpeculateCellOperand op1(this, node->child1());
     GPRReg op1GPR = op1.gpr();
@@ -8054,6 +8064,92 @@ void SpeculativeJIT::compileToStringOrCallStringConstructorOnCell(Node* node)
     }
 }
 
+void SpeculativeJIT::compileToStringOrCallStringConstructorOnNumber(Node* node)
+{
+    auto callToString = [&] (auto operation, GPRReg resultGPR, auto valueReg) {
+        flushRegisters();
+        callOperation(operation, resultGPR, valueReg, CCallHelpers::TrustedImm32(10));
+        m_jit.exceptionCheck();
+        cellResult(resultGPR, node);
+    };
+
+    switch (node->child1().useKind()) {
+    case Int32Use: {
+        SpeculateStrictInt32Operand value(this, node->child1());
+        GPRFlushedCallResult result(this);
+        callToString(operationInt32ToStringWithValidRadix, result.gpr(), value.gpr());
+        break;
+    }
+
+#if USE(JSVALUE64)
+    case Int52RepUse: {
+        SpeculateStrictInt52Operand value(this, node->child1());
+        GPRFlushedCallResult result(this);
+        callToString(operationInt52ToStringWithValidRadix, result.gpr(), value.gpr());
+        break;
+    }
+#endif
+
+    case DoubleRepUse: {
+        SpeculateDoubleOperand value(this, node->child1());
+        GPRFlushedCallResult result(this);
+        callToString(operationDoubleToStringWithValidRadix, result.gpr(), value.fpr());
+        break;
+    }
+
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+    }
+}
+
+void SpeculativeJIT::compileNumberToStringWithRadix(Node* node)
+{
+    bool validRadixIsGuaranteed = false;
+    if (node->child2()->isInt32Constant()) {
+        int32_t radix = node->child2()->asInt32();
+        if (radix >= 2 && radix <= 36)
+            validRadixIsGuaranteed = true;
+    }
+
+    auto callToString = [&] (auto operation, GPRReg resultGPR, auto valueReg, GPRReg radixGPR) {
+        flushRegisters();
+        callOperation(operation, resultGPR, valueReg, radixGPR);
+        m_jit.exceptionCheck();
+        cellResult(resultGPR, node);
+    };
+
+    switch (node->child1().useKind()) {
+    case Int32Use: {
+        SpeculateStrictInt32Operand value(this, node->child1());
+        SpeculateStrictInt32Operand radix(this, node->child2());
+        GPRFlushedCallResult result(this);
+        callToString(validRadixIsGuaranteed ? operationInt32ToStringWithValidRadix : operationInt32ToString, result.gpr(), value.gpr(), radix.gpr());
+        break;
+    }
+
+#if USE(JSVALUE64)
+    case Int52RepUse: {
+        SpeculateStrictInt52Operand value(this, node->child1());
+        SpeculateStrictInt32Operand radix(this, node->child2());
+        GPRFlushedCallResult result(this);
+        callToString(validRadixIsGuaranteed ? operationInt52ToStringWithValidRadix : operationInt52ToString, result.gpr(), value.gpr(), radix.gpr());
+        break;
+    }
+#endif
+
+    case DoubleRepUse: {
+        SpeculateDoubleOperand value(this, node->child1());
+        SpeculateStrictInt32Operand radix(this, node->child2());
+        GPRFlushedCallResult result(this);
+        callToString(validRadixIsGuaranteed ? operationDoubleToStringWithValidRadix : operationDoubleToString, result.gpr(), value.fpr(), radix.gpr());
+        break;
+    }
+
+    default:
+        RELEASE_ASSERT_NOT_REACHED();
+    }
+}
+
 void SpeculativeJIT::compileNewStringObject(Node* node)
 {
     SpeculateCellOperand operand(this, node->child1());
index e17e447..5a80d48 100644 (file)
@@ -1381,6 +1381,29 @@ public:
         return appendCallSetResult(operation, result);
     }
 
+    JITCompiler::Call callOperation(P_JITOperation_EZZ operation, GPRReg result, GPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
+
+    JITCompiler::Call callOperation(P_JITOperation_EZZ operation, GPRReg result, GPRReg arg1, TrustedImm32 arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
+
+    JITCompiler::Call callOperation(P_JITOperation_EDZ operation, GPRReg result, FPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
+
+    JITCompiler::Call callOperation(P_JITOperation_EDZ operation, GPRReg result, FPRReg arg1, TrustedImm32 arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
 
 #if USE(JSVALUE64)
     JITCompiler::Call callOperation(Z_JITOperation_EOJ operation, GPRReg result, GPRReg arg1, GPRReg arg2)
@@ -1610,6 +1633,17 @@ public:
         m_jit.setupArgumentsWithExecState(TrustedImm32(arg1), arg2);
         return appendCallSetResult(operation, result);
     }
+    JITCompiler::Call callOperation(P_JITOperation_EQZ operation, GPRReg result, GPRReg arg1, GPRReg arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
+    JITCompiler::Call callOperation(P_JITOperation_EQZ operation, GPRReg result, GPRReg arg1, TrustedImm32 arg2)
+    {
+        m_jit.setupArgumentsWithExecState(arg1, arg2);
+        return appendCallSetResult(operation, result);
+    }
+
     JITCompiler::Call callOperation(J_JITOperation_EZIcfZ operation, GPRReg result, int32_t arg1, InlineCallFrame* inlineCallFrame, GPRReg arg2)
     {
         m_jit.setupArgumentsWithExecState(TrustedImm32(arg1), TrustedImmPtr(inlineCallFrame), arg2);
@@ -2681,7 +2715,9 @@ public:
     void emitSwitchString(Node*, SwitchData*);
     void emitSwitch(Node*);
     
-    void compileToStringOrCallStringConstructorOnCell(Node*);
+    void compileToStringOrCallStringConstructor(Node*);
+    void compileToStringOrCallStringConstructorOnNumber(Node*);
+    void compileNumberToStringWithRadix(Node*);
     void compileNewStringObject(Node*);
     
     void compileNewTypedArray(Node*);
index dc76334..ced5495 100644 (file)
@@ -2835,6 +2835,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case NumberToStringWithRadix: {
+        compileNumberToStringWithRadix(node);
+        break;
+    }
+
     case GetByValWithThis: {
         JSValueOperand base(this, node->child1());
         JSValueRegs baseRegs = base.jsValueRegs();
@@ -3769,7 +3774,7 @@ void SpeculativeJIT::compile(Node* node)
         
     case ToString:
     case CallStringConstructor: {
-        compileToStringOrCallStringConstructorOnCell(node);
+        compileToStringOrCallStringConstructor(node);
         break;
     }
         
index a53a77a..326d143 100644 (file)
@@ -3739,7 +3739,7 @@ void SpeculativeJIT::compile(Node* node)
         
     case ToString:
     case CallStringConstructor: {
-        compileToStringOrCallStringConstructorOnCell(node);
+        compileToStringOrCallStringConstructor(node);
         break;
     }
         
@@ -5057,6 +5057,11 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case NumberToStringWithRadix: {
+        compileNumberToStringWithRadix(node);
+        break;
+    }
+
     case IsObject: {
         JSValueOperand value(this, node->child1());
         GPRTemporary result(this, Reuse, value);
index 51dd50e..9fdf9ef 100644 (file)
@@ -390,6 +390,37 @@ private:
             break;
         }
 
+        case ToString:
+        case CallStringConstructor: {
+            Edge& child1 = m_node->child1();
+            switch (child1.useKind()) {
+            case Int32Use:
+            case Int52RepUse:
+            case DoubleRepUse: {
+                if (child1->hasConstant()) {
+                    JSValue value = child1->constant()->value();
+                    if (value) {
+                        String result;
+                        if (value.isInt32())
+                            result = String::number(value.asInt32());
+                        else if (value.isNumber())
+                            result = String::numberToStringECMAScript(value.asNumber());
+
+                        if (!result.isNull()) {
+                            m_node->convertToLazyJSConstant(m_graph, LazyJSValue::newString(m_graph, result));
+                            m_changed = true;
+                        }
+                    }
+                }
+                break;
+            }
+
+            default:
+                break;
+            }
+            break;
+        }
+
         case GetArrayLength: {
             if (m_node->arrayMode().type() == Array::Generic
                 || m_node->arrayMode().type() == Array::String) {
index ff8aff0..ad54c94 100644 (file)
@@ -279,6 +279,7 @@ inline CapabilityLevel canCompile(Node* node)
     case DefineDataProperty:
     case DefineAccessorProperty:
     case ToLowerCase:
+    case NumberToStringWithRadix:
     case CheckDOM:
     case CallDOM:
     case CallDOMGetter:
index 1235307..6b4d508 100644 (file)
@@ -1078,6 +1078,9 @@ private:
         case ToLowerCase:
             compileToLowerCase();
             break;
+        case NumberToStringWithRadix:
+            compileNumberToStringWithRadix();
+            break;
         case CheckDOM:
             compileCheckDOM();
             break;
@@ -4981,6 +4984,18 @@ private:
             setJSValue(m_out.phi(Int64, simpleResult, convertedResult));
             return;
         }
+
+        case Int32Use:
+            setJSValue(vmCall(Int64, m_out.operation(operationInt32ToStringWithValidRadix), m_callFrame, lowInt32(m_node->child1()), m_out.constInt32(10)));
+            return;
+
+        case Int52RepUse:
+            setJSValue(vmCall(Int64, m_out.operation(operationInt52ToStringWithValidRadix), m_callFrame, lowStrictInt52(m_node->child1()), m_out.constInt32(10)));
+            return;
+
+        case DoubleRepUse:
+            setJSValue(vmCall(Int64, m_out.operation(operationDoubleToStringWithValidRadix), m_callFrame, lowDouble(m_node->child1()), m_out.constInt32(10)));
+            return;
             
         default:
             DFG_CRASH(m_graph, m_node, "Bad use kind");
@@ -9984,6 +9999,30 @@ private:
         setJSValue(m_out.phi(pointerType(), fastResult, slowResult));
     }
 
+    void compileNumberToStringWithRadix()
+    {
+        bool validRadixIsGuaranteed = false;
+        if (m_node->child2()->isInt32Constant()) {
+            int32_t radix = m_node->child2()->asInt32();
+            if (radix >= 2 && radix <= 36)
+                validRadixIsGuaranteed = true;
+        }
+
+        switch (m_node->child1().useKind()) {
+        case Int32Use:
+            setJSValue(vmCall(pointerType(), m_out.operation(validRadixIsGuaranteed ? operationInt32ToStringWithValidRadix : operationInt32ToString), m_callFrame, lowInt32(m_node->child1()), lowInt32(m_node->child2())));
+            break;
+        case Int52RepUse:
+            setJSValue(vmCall(pointerType(), m_out.operation(validRadixIsGuaranteed ? operationInt52ToStringWithValidRadix : operationInt52ToString), m_callFrame, lowStrictInt52(m_node->child1()), lowInt32(m_node->child2())));
+            break;
+        case DoubleRepUse:
+            setJSValue(vmCall(pointerType(), m_out.operation(validRadixIsGuaranteed ? operationDoubleToStringWithValidRadix : operationDoubleToString), m_callFrame, lowDouble(m_node->child1()), lowInt32(m_node->child2())));
+            break;
+        default:
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+    }
+
     void compileResolveScope()
     {
         UniquedStringImpl* uid = m_graph.identifiers()[m_node->identifierNumber()];
index 6288f9e..35b7d82 100644 (file)
@@ -892,6 +892,14 @@ public:
         addCallArgument(arg2);
     }
 
+    ALWAYS_INLINE void setupArgumentsWithExecState(FPRReg arg1, TrustedImm32 arg2)
+    {
+        resetCallArguments();
+        addCallArgument(GPRInfo::callFrameRegister);
+        addCallArgument(arg1);
+        addCallArgument(arg2);
+    }
+
     ALWAYS_INLINE void setupArgumentsWithExecState(GPRReg arg1, GPRReg arg2, FPRReg arg3)
     {
         resetCallArguments();
@@ -1093,6 +1101,20 @@ public:
         move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
     }
 
+    ALWAYS_INLINE void setupArgumentsWithExecState(FPRReg arg1, TrustedImm32 arg2)
+    {
+#if OS(WINDOWS) && CPU(X86_64)
+        // On Windows, arguments map to designated registers based on the argument positions, even when there are interlaced scalar and floating point arguments.
+        // See http://msdn.microsoft.com/en-us/library/zthk2dkh.aspx
+        moveDouble(arg1, FPRInfo::argumentFPR1);
+        move(arg2, GPRInfo::argumentGPR2);
+#else
+        moveDouble(arg1, FPRInfo::argumentFPR0);
+        move(arg2, GPRInfo::argumentGPR1);
+#endif
+        move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+    }
+
     ALWAYS_INLINE void setupArgumentsWithExecState(GPRReg arg1, GPRReg arg2, FPRReg arg3)
     {
 #if OS(WINDOWS) && CPU(X86_64)
@@ -1135,6 +1157,13 @@ public:
         move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
     }
 
+    ALWAYS_INLINE void setupArgumentsWithExecState(FPRReg arg1, TrustedImm32 arg2)
+    {
+        moveDouble(arg1, FPRInfo::argumentFPR0);
+        move(arg2, GPRInfo::argumentGPR1);
+        move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+    }
+
     ALWAYS_INLINE void setupArgumentsWithExecState(GPRReg arg1, GPRReg arg2, FPRReg arg3)
     {
         moveDouble(arg3, FPRInfo::argumentFPR0);
@@ -1175,6 +1204,13 @@ public:
         move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
     }
 
+    ALWAYS_INLINE void setupArgumentsWithExecState(FPRReg arg1, TrustedImm32 arg2)
+    {
+        move(arg2, GPRInfo::argumentGPR3);
+        assembler().vmov(GPRInfo::argumentGPR1, GPRInfo::argumentGPR2, arg1);
+        move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+    }
+
     ALWAYS_INLINE void setupArgumentsWithExecState(GPRReg arg1, GPRReg arg2, FPRReg arg3)
     {
         setupStubArguments(arg1, arg2);
@@ -1228,6 +1264,13 @@ public:
         poke(arg2, 4);
     }
 
+    ALWAYS_INLINE void setupArgumentsWithExecState(FPRReg arg1, TrustedImm32 arg2)
+    {
+        assembler().vmov(GPRInfo::argumentGPR2, GPRInfo::argumentGPR3, arg1);
+        move(GPRInfo::callFrameRegister, GPRInfo::argumentGPR0);
+        poke(arg2, 4);
+    }
+
     ALWAYS_INLINE void setupArgumentsWithExecState(GPRReg arg1, GPRReg arg2, FPRReg arg3)
     {
         setupStubArguments(arg1, arg2);
index 1cdf136..233b3ed 100644 (file)
@@ -312,6 +312,8 @@ typedef char* (JIT_OPERATION *P_JITOperation_EStZ)(ExecState*, Structure*, int32
 typedef char* (JIT_OPERATION *P_JITOperation_EStZB)(ExecState*, Structure*, int32_t, Butterfly*);
 typedef char* (JIT_OPERATION *P_JITOperation_EStZP)(ExecState*, Structure*, int32_t, char*);
 typedef char* (JIT_OPERATION *P_JITOperation_EZZ)(ExecState*, int32_t, int32_t);
+typedef char* (JIT_OPERATION *P_JITOperation_EQZ)(ExecState*, int64_t, int32_t);
+typedef char* (JIT_OPERATION *P_JITOperation_EDZ)(ExecState*, double, int32_t);
 typedef SlowPathReturnType (JIT_OPERATION *Sprt_JITOperation_ECli)(ExecState*, CallLinkInfo*);
 typedef StringImpl* (JIT_OPERATION *T_JITOperation_EJss)(ExecState*, JSString*);
 typedef JSString* (JIT_OPERATION *Jss_JITOperation_EZ)(ExecState*, int32_t);
index 263b93e..7713a9e 100644 (file)
@@ -56,6 +56,8 @@ enum JS_EXPORT_PRIVATE Intrinsic {
     StringPrototypeValueOfIntrinsic,
     StringPrototypeReplaceIntrinsic,
     StringPrototypeReplaceRegExpIntrinsic,
+    StringPrototypeToLowerCaseIntrinsic,
+    NumberPrototypeToStringIntrinsic,
     IMulIntrinsic,
     RandomIntrinsic,
     FRoundIntrinsic,
@@ -78,7 +80,6 @@ enum JS_EXPORT_PRIVATE Intrinsic {
     AtomicsWaitIntrinsic,
     AtomicsWakeIntrinsic,
     AtomicsXorIntrinsic,
-    ToLowerCaseIntrinsic,
     ParseIntIntrinsic,
 
     // Getter intrinsics.
index e0ccc14..94e51c0 100644 (file)
@@ -58,7 +58,7 @@ const ClassInfo NumberPrototype::s_info = { "Number", &NumberObject::s_info, &nu
 
 /* Source for NumberPrototype.lut.h
 @begin numberPrototypeTable
-  toString          numberProtoFuncToString         DontEnum|Function 1
+  toString          numberProtoFuncToString         DontEnum|Function 1 NumberPrototypeToStringIntrinsic
   toLocaleString    numberProtoFuncToLocaleString   DontEnum|Function 0
   valueOf           numberProtoFuncValueOf          DontEnum|Function 0
   toFixed           numberProtoFuncToFixed          DontEnum|Function 1
@@ -144,7 +144,7 @@ typedef char RadixBuffer[2180];
 // Mapping from integers 0..35 to digit identifying this value, for radix 2..36.
 static const char radixDigits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
 
-static char* int52ToStringWithRadix(char* startOfResultString, int64_t int52Value, unsigned radix)
+static inline char* int52ToStringWithRadix(char* startOfResultString, int64_t int52Value, unsigned radix)
 {
     bool negative = false;
     uint64_t positiveNumber = int52Value;
@@ -499,25 +499,68 @@ static inline int32_t extractRadixFromArgs(ExecState* exec)
     return radix;
 }
 
-static inline EncodedJSValue integerValueToString(ExecState* exec, int32_t radix, int32_t value)
+static inline JSString* int32ToStringInternal(VM& vm, int32_t value, int32_t radix)
 {
+    ASSERT(!(radix < 2 || radix > 36));
     // A negative value casted to unsigned would be bigger than 36 (the max radix).
     if (static_cast<unsigned>(value) < static_cast<unsigned>(radix)) {
         ASSERT(value <= 36);
         ASSERT(value >= 0);
-        VM* vm = &exec->vm();
-        return JSValue::encode(vm->smallStrings.singleCharacterString(radixDigits[value]));
+        return vm.smallStrings.singleCharacterString(radixDigits[value]);
     }
 
     if (radix == 10) {
-        VM* vm = &exec->vm();
-        return JSValue::encode(jsString(vm, vm->numericStrings.add(value)));
+        return jsString(&vm, vm.numericStrings.add(value));
     }
 
-    return JSValue::encode(jsString(exec, toStringWithRadix(value, radix)));
+    return jsString(&vm, toStringWithRadix(value, radix));
 
 }
 
+static ALWAYS_INLINE JSString* numberToStringInternal(VM& vm, double doubleValue, int32_t radix)
+{
+    ASSERT(!(radix < 2 || radix > 36));
+
+    int32_t integerValue = static_cast<int32_t>(doubleValue);
+    if (integerValue == doubleValue)
+        return int32ToStringInternal(vm, integerValue, radix);
+
+    if (radix == 10)
+        return jsString(&vm, vm.numericStrings.add(doubleValue));
+
+    if (!std::isfinite(doubleValue))
+        return jsNontrivialString(&vm, String::numberToStringECMAScript(doubleValue));
+
+    RadixBuffer buffer;
+    return jsString(&vm, toStringWithRadix(buffer, doubleValue, radix));
+}
+
+JSString* int32ToString(VM& vm, int32_t value, int32_t radix)
+{
+    return int32ToStringInternal(vm, value, radix);
+}
+
+JSString* int52ToString(VM& vm, int64_t value, int32_t radix)
+{
+    ASSERT(!(radix < 2 || radix > 36));
+    if (radix == 10)
+        return jsString(&vm, vm.numericStrings.add(static_cast<double>(value)));
+
+    // Position the decimal point at the center of the string, set
+    // the startOfResultString pointer to point at the decimal point.
+    RadixBuffer buffer;
+    char* decimalPoint = buffer + sizeof(buffer) / 2;
+    char* startOfResultString = decimalPoint;
+    *decimalPoint = '\0';
+
+    return jsString(&vm, int52ToStringWithRadix(startOfResultString, value, radix));
+}
+
+JSString* numberToString(VM& vm, double doubleValue, int32_t radix)
+{
+    return numberToStringInternal(vm, doubleValue, radix);
+}
+
 EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* exec)
 {
     VM& vm = exec->vm();
@@ -531,18 +574,7 @@ EncodedJSValue JSC_HOST_CALL numberProtoFuncToString(ExecState* exec)
     if (radix < 2 || radix > 36)
         return throwVMError(exec, scope, createRangeError(exec, ASCIILiteral("toString() radix argument must be between 2 and 36")));
 
-    int32_t integerValue = static_cast<int32_t>(doubleValue);
-    if (integerValue == doubleValue)
-        return integerValueToString(exec, radix, integerValue);
-
-    if (radix == 10)
-        return JSValue::encode(jsString(&vm, vm.numericStrings.add(doubleValue)));
-
-    if (!std::isfinite(doubleValue))
-        return JSValue::encode(jsNontrivialString(exec, String::numberToStringECMAScript(doubleValue)));
-
-    RadixBuffer s;
-    return JSValue::encode(jsString(exec, toStringWithRadix(s, doubleValue, radix)));
+    return JSValue::encode(numberToStringInternal(vm, doubleValue, radix));
 }
 
 EncodedJSValue JSC_HOST_CALL numberProtoFuncToLocaleString(ExecState* exec)
index 102a214..3c069cb 100644 (file)
@@ -51,5 +51,8 @@ private:
 };
 
 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);
 
 } // namespace JSC
index dabbc26..afb65e1 100644 (file)
@@ -142,7 +142,7 @@ void StringPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject, JSStr
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("slice", stringProtoFuncSlice, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substr", stringProtoFuncSubstr, DontEnum, 2);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("substring", stringProtoFuncSubstring, DontEnum, 2);
-    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0, ToLowerCaseIntrinsic);
+    JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION("toLowerCase", stringProtoFuncToLowerCase, DontEnum, 0, StringPrototypeToLowerCaseIntrinsic);
     JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toUpperCase", stringProtoFuncToUpperCase, DontEnum, 0);
 #if ENABLE(INTL)
     JSC_BUILTIN_FUNCTION_WITHOUT_TRANSITION("localeCompare", stringPrototypeLocaleCompareCodeGenerator, DontEnum);