[JSC] Add basic DFG/FTL support for Math.round
authorbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 May 2015 00:23:32 +0000 (00:23 +0000)
committerbenjamin@webkit.org <benjamin@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 May 2015 00:23:32 +0000 (00:23 +0000)
https://bugs.webkit.org/show_bug.cgi?id=144725

Patch by Benjamin Poulain <bpoulain@apple.com> on 2015-05-07
Reviewed by Filip Pizlo.

This patch adds two optimizations targeting Math.round():
-Add a DFGNode ArithRound corresponding to the intrinsic RoundIntrinsic.
-Change the MacroAssembler to be stricter on how we fail to convert a double
 to ingeter. Previously, any number valued zero would fail, now we only
 fail for -0.

Since ArithRound speculate it produces int32, the MacroAssembler assembler
part became necessary because zero is a pretty common output of Math.round()
and we would OSR exit a lot (and eventually recompile for doubles).

The implementation itself of the inline Math.round() is exactly the same
as the C function that exists for Math.round(). We can very likely do better
but it is a good start known to be valid and inlining alone alread provides
significant speedups.

* assembler/X86Assembler.h:
(JSC::X86Assembler::movmskpd_rr):
* assembler/MacroAssemblerX86Common.h:
(JSC::MacroAssemblerX86Common::branchConvertDoubleToInt32):
When we have a zero, get the sign bit out of the double and check if is one.

I'll look into doing the same improvement for ARM.

* bytecode/SpeculatedType.cpp:
(JSC::typeOfDoubleRounding):
(JSC::typeOfDoubleFRound): Deleted.
* bytecode/SpeculatedType.h:
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::handleIntrinsic):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGGraph.h:
(JSC::DFG::Graph::roundShouldSpeculateInt32):
(JSC::DFG::Graph::negateShouldSpeculateMachineInt): Deleted.
* dfg/DFGNode.h:
(JSC::DFG::Node::arithNodeFlags):
(JSC::DFG::Node::hasHeapPrediction):
(JSC::DFG::Node::hasArithMode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileArithRound):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLIntrinsicRepository.h:
* ftl/FTLLowerDFGToLLVM.cpp:
(JSC::FTL::LowerDFGToLLVM::compileNode):
(JSC::FTL::LowerDFGToLLVM::convertDoubleToInt32):
(JSC::FTL::LowerDFGToLLVM::compileDoubleAsInt32):
(JSC::FTL::LowerDFGToLLVM::compileArithRound):
* ftl/FTLOutput.h:
(JSC::FTL::Output::ceil64):
* jit/ThunkGenerators.cpp:
* runtime/MathCommon.cpp:
* runtime/MathCommon.h:
* runtime/MathObject.cpp:
(JSC::mathProtoFuncRound):
* tests/stress/math-round-basics.js: Added.
(mathRoundOnIntegers):
(mathRoundOnDoubles):
(mathRoundOnBooleans):
(uselessMathRound):
(mathRoundWithOverflow):
(mathRoundConsumedAsDouble):
(mathRoundDoesNotCareAboutMinusZero):
(mathRoundNoArguments):
(mathRoundTooManyArguments):
(testMathRoundOnConstants):
(mathRoundStructTransition):
(Math.round):

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

29 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/assembler/MacroAssemblerX86Common.h
Source/JavaScriptCore/assembler/X86Assembler.h
Source/JavaScriptCore/bytecode/SpeculatedType.cpp
Source/JavaScriptCore/bytecode/SpeculatedType.h
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGArithMode.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
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/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLIntrinsicRepository.h
Source/JavaScriptCore/ftl/FTLLowerDFGToLLVM.cpp
Source/JavaScriptCore/ftl/FTLOutput.h
Source/JavaScriptCore/jit/ThunkGenerators.cpp
Source/JavaScriptCore/runtime/MathCommon.cpp
Source/JavaScriptCore/runtime/MathCommon.h
Source/JavaScriptCore/runtime/MathObject.cpp
Source/JavaScriptCore/tests/stress/math-round-basics.js [new file with mode: 0644]

index a468f5a..a890f74 100644 (file)
@@ -1,3 +1,95 @@
+2015-05-07  Benjamin Poulain  <bpoulain@apple.com>
+
+        [JSC] Add basic DFG/FTL support for Math.round
+        https://bugs.webkit.org/show_bug.cgi?id=144725
+
+        Reviewed by Filip Pizlo.
+
+        This patch adds two optimizations targeting Math.round():
+        -Add a DFGNode ArithRound corresponding to the intrinsic RoundIntrinsic.
+        -Change the MacroAssembler to be stricter on how we fail to convert a double
+         to ingeter. Previously, any number valued zero would fail, now we only
+         fail for -0.
+
+        Since ArithRound speculate it produces int32, the MacroAssembler assembler
+        part became necessary because zero is a pretty common output of Math.round()
+        and we would OSR exit a lot (and eventually recompile for doubles).
+
+        The implementation itself of the inline Math.round() is exactly the same
+        as the C function that exists for Math.round(). We can very likely do better
+        but it is a good start known to be valid and inlining alone alread provides
+        significant speedups.
+
+        * assembler/X86Assembler.h:
+        (JSC::X86Assembler::movmskpd_rr):
+        * assembler/MacroAssemblerX86Common.h:
+        (JSC::MacroAssemblerX86Common::branchConvertDoubleToInt32):
+        When we have a zero, get the sign bit out of the double and check if is one.
+
+        I'll look into doing the same improvement for ARM.
+
+        * bytecode/SpeculatedType.cpp:
+        (JSC::typeOfDoubleRounding):
+        (JSC::typeOfDoubleFRound): Deleted.
+        * bytecode/SpeculatedType.h:
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::handleIntrinsic):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGGraph.h:
+        (JSC::DFG::Graph::roundShouldSpeculateInt32):
+        (JSC::DFG::Graph::negateShouldSpeculateMachineInt): Deleted.
+        * dfg/DFGNode.h:
+        (JSC::DFG::Node::arithNodeFlags):
+        (JSC::DFG::Node::hasHeapPrediction):
+        (JSC::DFG::Node::hasArithMode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        (JSC::DFG::PredictionPropagationPhase::propagate):
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileArithRound):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLIntrinsicRepository.h:
+        * ftl/FTLLowerDFGToLLVM.cpp:
+        (JSC::FTL::LowerDFGToLLVM::compileNode):
+        (JSC::FTL::LowerDFGToLLVM::convertDoubleToInt32):
+        (JSC::FTL::LowerDFGToLLVM::compileDoubleAsInt32):
+        (JSC::FTL::LowerDFGToLLVM::compileArithRound):
+        * ftl/FTLOutput.h:
+        (JSC::FTL::Output::ceil64):
+        * jit/ThunkGenerators.cpp:
+        * runtime/MathCommon.cpp:
+        * runtime/MathCommon.h:
+        * runtime/MathObject.cpp:
+        (JSC::mathProtoFuncRound):
+        * tests/stress/math-round-basics.js: Added.
+        (mathRoundOnIntegers):
+        (mathRoundOnDoubles):
+        (mathRoundOnBooleans):
+        (uselessMathRound):
+        (mathRoundWithOverflow):
+        (mathRoundConsumedAsDouble):
+        (mathRoundDoesNotCareAboutMinusZero):
+        (mathRoundNoArguments):
+        (mathRoundTooManyArguments):
+        (testMathRoundOnConstants):
+        (mathRoundStructTransition):
+        (Math.round):
+
 2015-05-07  Saam Barati  <saambarati1@gmail.com>
 
         exceptionFuzz tests should explicitly initialize the exceptionFuzz boolean in JavaScript code through a function in jsc.cpp
index 4a8be5a..b3b5074 100644 (file)
@@ -921,8 +921,17 @@ public:
         m_assembler.cvttsd2si_rr(src, dest);
 
         // If the result is zero, it might have been -0.0, and the double comparison won't catch this!
+#if CPU(X86_64)
+        if (negZeroCheck) {
+            Jump valueIsNonZero = branchTest32(NonZero, dest);
+            m_assembler.movmskpd_rr(src, scratchRegister);
+            failureCases.append(branchTest32(NonZero, scratchRegister, TrustedImm32(1)));
+            valueIsNonZero.link(this);
+        }
+#else
         if (negZeroCheck)
             failureCases.append(branchTest32(Zero, dest));
+#endif
 
         // Convert the integer result back to float & compare to the original value - if not equal or unordered (NaN) then jump.
         convertInt32ToDouble(dest, fpTemp);
index b88e3c3..ce07309 100644 (file)
@@ -253,6 +253,7 @@ private:
         OP2_CVTSS2SD_VsdWsd = 0x5A,
         OP2_SUBSD_VsdWsd    = 0x5C,
         OP2_DIVSD_VsdWsd    = 0x5E,
+        OP2_MOVMSKPD_VdEd   = 0x50,
         OP2_SQRTSD_VsdWsd   = 0x51,
         OP2_ANDNPD_VpdWpd   = 0x55,
         OP2_XORPD_VpdWpd    = 0x57,
@@ -1804,6 +1805,12 @@ public:
         m_formatter.twoByteOp(OP2_MOVD_VdEd, (RegisterID)dst, src);
     }
 
+    void movmskpd_rr(XMMRegisterID src, RegisterID dst)
+    {
+        m_formatter.prefix(PRE_SSE_66);
+        m_formatter.twoByteOp64(OP2_MOVMSKPD_VdEd, dst, (RegisterID)src);
+    }
+
 #if CPU(X86_64)
     void movq_rr(XMMRegisterID src, RegisterID dst)
     {
index 5f44f14..68a5d95 100644 (file)
@@ -507,7 +507,7 @@ SpeculatedType typeOfDoubleAbs(SpeculatedType value)
     return typeOfDoubleNegation(value);
 }
 
-SpeculatedType typeOfDoubleFRound(SpeculatedType value)
+SpeculatedType typeOfDoubleRounding(SpeculatedType value)
 {
     // We might lose bits, which leads to a NaN being purified.
     if (value & SpecDoubleImpureNaN)
index b60e382..2882337 100644 (file)
@@ -422,7 +422,7 @@ SpeculatedType typeOfDoubleQuotient(SpeculatedType, SpeculatedType);
 SpeculatedType typeOfDoubleMinMax(SpeculatedType, SpeculatedType);
 SpeculatedType typeOfDoubleNegation(SpeculatedType);
 SpeculatedType typeOfDoubleAbs(SpeculatedType);
-SpeculatedType typeOfDoubleFRound(SpeculatedType);
+SpeculatedType typeOfDoubleRounding(SpeculatedType);
 SpeculatedType typeOfDoublePow(SpeculatedType, SpeculatedType);
 
 // This conservatively models the behavior of arbitrary double operations.
index 53c845f..68a62e9 100644 (file)
@@ -761,6 +761,36 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         forNode(node).setType(typeOfDoublePow(forNode(node->child1()).m_type, forNode(node->child2()).m_type));
         break;
     }
+
+    case ArithRound: {
+        JSValue operand = forNode(node->child1()).value();
+        if (operand && operand.isNumber()) {
+            double roundedValue = jsRound(operand.asNumber());
+
+            if (producesInteger(node->arithRoundingMode())) {
+                int32_t roundedValueAsInt32 = static_cast<int32_t>(roundedValue);
+                if (roundedValueAsInt32 == roundedValue) {
+                    if (shouldCheckNegativeZero(node->arithRoundingMode())) {
+                        if (roundedValueAsInt32 || !std::signbit(roundedValue)) {
+                            setConstant(node, jsNumber(roundedValueAsInt32));
+                            break;
+                        }
+                    } else {
+                        setConstant(node, jsNumber(roundedValueAsInt32));
+                        break;
+                    }
+                }
+            } else {
+                setConstant(node, jsDoubleNumber(roundedValue));
+                break;
+            }
+        }
+        if (producesInteger(node->arithRoundingMode()))
+            forNode(node).setType(SpecInt32);
+        else
+            forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
+        break;
+    }
             
     case ArithSqrt: {
         JSValue child = forNode(node->child1()).value();
@@ -778,7 +808,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
             setConstant(node, jsDoubleNumber(static_cast<float>(child.asNumber())));
             break;
         }
-        forNode(node).setType(typeOfDoubleFRound(forNode(node->child1()).m_type));
+        forNode(node).setType(typeOfDoubleRounding(forNode(node->child1()).m_type));
         break;
     }
         
index 064f064..4e09ac3 100644 (file)
@@ -40,6 +40,14 @@ enum Mode {
     CheckOverflowAndNegativeZero, // Check for both overflow and negative zero.
     DoOverflow // Up-convert to the smallest type that soundly represents all possible results after input type speculation.
 };
+
+// Define the type of operation the rounding operation will perform.
+enum class RoundingMode {
+    Int32, // The round operation produces a integer and -0 is considered as 0.
+    Int32WithNegativeZeroCheck, // The round operation produces a integer and checks for -0.
+    Double // The round operation produce a double. The result can be -0, NaN or (+/-)Infinity.
+};
+
 } // namespace Arith
 
 inline bool doesOverflow(Arith::Mode mode)
@@ -122,6 +130,16 @@ inline bool subsumes(Arith::Mode earlier, Arith::Mode later)
     }
 }
 
+inline bool producesInteger(Arith::RoundingMode mode)
+{
+    return mode == Arith::RoundingMode::Int32WithNegativeZeroCheck || mode == Arith::RoundingMode::Int32;
+}
+
+inline bool shouldCheckNegativeZero(Arith::RoundingMode mode)
+{
+    return mode == Arith::RoundingMode::Int32WithNegativeZeroCheck;
+}
+
 } } // namespace JSC::DFG
 
 namespace WTF {
index 4c6bbdd..abc51d4 100644 (file)
@@ -2044,7 +2044,21 @@ bool ByteCodeParser::handleIntrinsic(int resultOperand, Intrinsic intrinsic, int
         
         return true;
     }
-
+    case RoundIntrinsic: {
+        if (argumentCountIncludingThis == 1) {
+            insertChecks();
+            set(VirtualRegister(resultOperand), addToGraph(JSConstant, OpInfo(m_constantNaN)));
+            return true;
+        }
+        if (argumentCountIncludingThis == 2) {
+            insertChecks();
+            Node* operand = get(virtualRegisterForArgument(1, registerOffset));
+            Node* roundNode = addToGraph(ArithRound, OpInfo(0), OpInfo(prediction), operand);
+            set(VirtualRegister(resultOperand), roundNode);
+            return true;
+        }
+        return false;
+    }
     case IMulIntrinsic: {
         if (argumentCountIncludingThis != 3)
             return false;
index 60a8994..350b20d 100644 (file)
@@ -134,6 +134,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case ArithPow:
     case ArithSqrt:
     case ArithFRound:
+    case ArithRound:
     case ArithSin:
     case ArithCos:
     case ArithLog:
index a9e30ac..1117e7a 100644 (file)
@@ -83,6 +83,7 @@ bool doesGC(Graph& graph, Node* node)
     case ArithMax:
     case ArithPow:
     case ArithSqrt:
+    case ArithRound:
     case ArithFRound:
     case ArithSin:
     case ArithCos:
index 7ab1b16..eead3fa 100644 (file)
@@ -322,6 +322,28 @@ private:
             fixDoubleOrBooleanEdge(node->child2());
             break;
         }
+
+        case ArithRound: {
+            if (node->child1()->shouldSpeculateInt32OrBooleanForArithmetic() && node->canSpeculateInt32(FixupPass)) {
+                fixIntOrBooleanEdge(node->child1());
+                insertCheck<Int32Use>(m_indexInBlock, node->child1().node());
+                node->convertToIdentity();
+                break;
+            }
+            fixDoubleOrBooleanEdge(node->child1());
+
+            if (isInt32OrBooleanSpeculation(node->getHeapPrediction()) && m_graph.roundShouldSpeculateInt32(node, FixupPass)) {
+                node->setResult(NodeResultInt32);
+                if (bytecodeCanIgnoreNegativeZero(node->arithNodeFlags()))
+                    node->setArithRoundingMode(Arith::RoundingMode::Int32);
+                else
+                    node->setArithRoundingMode(Arith::RoundingMode::Int32WithNegativeZeroCheck);
+            } else {
+                node->setResult(NodeResultDouble);
+                node->setArithRoundingMode(Arith::RoundingMode::Double);
+            }
+            break;
+        }
             
         case ArithSqrt:
         case ArithFRound:
index d92e7c9..65a63e0 100644 (file)
@@ -314,6 +314,12 @@ public:
             && !hasExitSite(negate, Int52Overflow)
             && negate->canSpeculateInt52(pass);
     }
+
+    bool roundShouldSpeculateInt32(Node* arithRound, PredictionPass pass)
+    {
+        ASSERT(arithRound->op() == ArithRound);
+        return arithRound->canSpeculateInt32(pass) && !hasExitSite(arithRound->origin.semantic, Overflow) && !hasExitSite(arithRound->origin.semantic, NegativeZero);
+    }
     
     static const char *opName(NodeType);
     
index 2d775f5..0a42c4a 100644 (file)
@@ -872,7 +872,7 @@ struct Node {
     NodeFlags arithNodeFlags()
     {
         NodeFlags result = m_flags & NodeArithFlagsMask;
-        if (op() == ArithMul || op() == ArithDiv || op() == ArithMod || op() == ArithNegate || op() == ArithPow || op() == DoubleAsInt32)
+        if (op() == ArithMul || op() == ArithDiv || op() == ArithMod || op() == ArithNegate || op() == ArithPow || op() == ArithRound || op() == DoubleAsInt32)
             return result;
         return result & ~NodeBytecodeNeedsNegZero;
     }
@@ -1242,6 +1242,7 @@ struct Node {
     bool hasHeapPrediction()
     {
         switch (op()) {
+        case ArithRound:
         case GetDirectPname:
         case GetById:
         case GetByIdFlush:
@@ -1563,6 +1564,23 @@ struct Node {
     {
         m_opInfo = mode;
     }
+
+    bool hasArithRoundingMode()
+    {
+        return op() == ArithRound;
+    }
+
+    Arith::RoundingMode arithRoundingMode()
+    {
+        ASSERT(hasArithRoundingMode());
+        return static_cast<Arith::RoundingMode>(m_opInfo);
+    }
+
+    void setArithRoundingMode(Arith::RoundingMode mode)
+    {
+        ASSERT(hasArithRoundingMode());
+        m_opInfo = static_cast<uintptr_t>(mode);
+    }
     
     bool hasVirtualRegister()
     {
index 75c8842..2ce75bc 100644 (file)
@@ -150,6 +150,7 @@ namespace JSC { namespace DFG {
     macro(ArithMax, NodeResultNumber) \
     macro(ArithFRound, NodeResultNumber) \
     macro(ArithPow, NodeResultNumber) \
+    macro(ArithRound, NodeResultNumber) \
     macro(ArithSqrt, NodeResultNumber) \
     macro(ArithSin, NodeResultNumber) \
     macro(ArithCos, NodeResultNumber) \
index 31d66ab..0ff5a36 100644 (file)
@@ -347,7 +347,15 @@ private:
             changed |= setPrediction(SpecBytecodeDouble);
             break;
         }
-            
+
+        case ArithRound: {
+            if (isInt32OrBooleanSpeculation(node->getHeapPrediction()) && m_graph.roundShouldSpeculateInt32(node, m_pass))
+                changed |= setPrediction(SpecInt32);
+            else
+                changed |= setPrediction(SpecBytecodeDouble);
+            break;
+        }
+
         case ArithAbs: {
             SpeculatedType child = node->child1()->prediction();
             if (isInt32OrBooleanSpeculationForArithmetic(child)
index f4b079f..4d8d74b 100644 (file)
@@ -154,6 +154,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     case ArithPow:
     case ArithSqrt:
     case ArithFRound:
+    case ArithRound:
     case ArithSin:
     case ArithCos:
     case ArithLog:
index 36d9bf0..fa37a71 100644 (file)
@@ -3544,6 +3544,31 @@ void SpeculativeJIT::compileArithMod(Node* node)
     }
 }
 
+void SpeculativeJIT::compileArithRound(Node* node)
+{
+    ASSERT(node->child1().useKind() == DoubleRepUse);
+
+    SpeculateDoubleOperand value(this, node->child1());
+    FPRReg valueFPR = value.fpr();
+    flushRegisters();
+    FPRResult roundedResultAsDouble(this);
+    FPRReg resultFPR = roundedResultAsDouble.fpr();
+    callOperation(jsRound, resultFPR, valueFPR);
+
+    if (producesInteger(node->arithRoundingMode())) {
+        GPRTemporary roundedResultAsInt32(this);
+        FPRTemporary scratch(this);
+        FPRReg scratchFPR = scratch.fpr();
+        GPRReg resultGPR = roundedResultAsInt32.gpr();
+        JITCompiler::JumpList failureCases;
+        m_jit.branchConvertDoubleToInt32(resultFPR, resultGPR, failureCases, scratchFPR, shouldCheckNegativeZero(node->arithRoundingMode()));
+        speculationCheck(Overflow, JSValueRegs(), node, failureCases);
+
+        int32Result(resultGPR, node);
+    } else
+        doubleResult(resultFPR, node);
+}
+
 void SpeculativeJIT::compileArithSqrt(Node* node)
 {
     SpeculateDoubleOperand op1(this, node->child1());
index 67e673b..b9e6e0d 100644 (file)
@@ -2199,6 +2199,7 @@ public:
     void compileArithDiv(Node*);
     void compileArithMod(Node*);
     void compileArithPow(Node*);
+    void compileArithRound(Node*);
     void compileArithSqrt(Node*);
     void compileArithLog(Node*);
     void compileConstantStoragePointer(Node*);
index 23cf244..1d0dda7 100644 (file)
@@ -2170,6 +2170,10 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ArithRound:
+        compileArithRound(node);
+        break;
+
     case ArithSin: {
         SpeculateDoubleOperand op1(this, node->child1());
         FPRReg op1FPR = op1.fpr();
index 3e574d4..b770b50 100644 (file)
@@ -2313,6 +2313,10 @@ void SpeculativeJIT::compile(Node* node)
         break;
     }
 
+    case ArithRound:
+        compileArithRound(node);
+        break;
+
     case ArithSin: {
         SpeculateDoubleOperand op1(this, node->child1());
         FPRReg op1FPR = op1.fpr();
index 0bfdfcc..26eca7e 100644 (file)
@@ -63,6 +63,7 @@ inline CapabilityLevel canCompile(Node* node)
     case BitLShift:
     case BitURShift:
     case CheckStructure:
+    case DoubleAsInt32:
     case ArrayifyToStructure:
     case PutStructure:
     case GetButterfly:
@@ -89,6 +90,7 @@ inline CapabilityLevel canCompile(Node* node)
     case ArithSin:
     case ArithCos:
     case ArithPow:
+    case ArithRound:
     case ArithSqrt:
     case ArithLog:
     case ArithFRound:
index 3d66eca..4b28d35 100644 (file)
@@ -35,6 +35,7 @@
 namespace JSC { namespace FTL {
 
 #define FOR_EACH_FTL_INTRINSIC(macro) \
+    macro(ceil64, "llvm.ceil.f64", functionType(doubleType, doubleType)) \
     macro(ctlz32, "llvm.ctlz.i32", functionType(int32, int32, boolean)) \
     macro(addWithOverflow32, "llvm.sadd.with.overflow.i32", functionType(structType(m_context, int32, boolean), int32, int32)) \
     macro(addWithOverflow64, "llvm.sadd.with.overflow.i64", functionType(structType(m_context, int64, boolean), int64, int64)) \
index 19281a9..b91e9c1 100644 (file)
@@ -443,6 +443,9 @@ private:
         case DoubleRep:
             compileDoubleRep();
             break;
+        case DoubleAsInt32:
+            compileDoubleAsInt32();
+            break;
         case ValueRep:
             compileValueRep();
             break;
@@ -505,6 +508,9 @@ private:
         case ArithPow:
             compileArithPow();
             break;
+        case ArithRound:
+            compileArithRound();
+            break;
         case ArithSqrt:
             compileArithSqrt();
             break;
@@ -970,7 +976,13 @@ private:
             DFG_CRASH(m_graph, m_node, "Bad use kind");
         }
     }
-    
+
+    void compileDoubleAsInt32()
+    {
+        LValue integerValue = convertDoubleToInt32(lowDouble(m_node->child1()), shouldCheckNegativeZero(m_node->arithMode()));
+        setInt32(integerValue);
+    }
+
     void compileValueRep()
     {
         switch (m_node->child1().useKind()) {
@@ -1761,6 +1773,34 @@ private:
         }
     }
 
+    void compileArithRound()
+    {
+        LBasicBlock realPartIsMoreThanHalf = FTL_NEW_BLOCK(m_out, ("ArithRound should round down"));
+        LBasicBlock continuation = FTL_NEW_BLOCK(m_out, ("ArithRound continuation"));
+
+        LValue value = lowDouble(m_node->child1());
+        LValue integerValue = m_out.ceil64(value);
+        ValueFromBlock integerValueResult = m_out.anchor(integerValue);
+
+        LValue realPart = m_out.doubleSub(integerValue, value);
+
+        m_out.branch(m_out.doubleGreaterThanOrUnordered(realPart, m_out.constDouble(0.5)), unsure(realPartIsMoreThanHalf), unsure(continuation));
+
+        LBasicBlock lastNext = m_out.appendTo(realPartIsMoreThanHalf, continuation);
+        LValue integerValueRoundedDown = m_out.doubleSub(integerValue, m_out.constDouble(1));
+        ValueFromBlock integerValueRoundedDownResult = m_out.anchor(integerValueRoundedDown);
+        m_out.jump(continuation);
+        m_out.appendTo(continuation, lastNext);
+
+        LValue result = m_out.phi(m_out.doubleType, integerValueResult, integerValueRoundedDownResult);
+
+        if (producesInteger(m_node->arithRoundingMode())) {
+            LValue integerValue = convertDoubleToInt32(result, shouldCheckNegativeZero(m_node->arithRoundingMode()));
+            setInt32(integerValue);
+        } else
+            setDouble(result);
+    }
+
     void compileArithSqrt() { setDouble(m_out.doubleSqrt(lowDouble(m_node->child1()))); }
 
     void compileArithLog() { setDouble(m_out.doubleLog(lowDouble(m_node->child1()))); }
@@ -7235,7 +7275,31 @@ private:
         
         return possibleResult;
     }
-    
+
+    LValue convertDoubleToInt32(LValue value, bool shouldCheckNegativeZero)
+    {
+        LValue integerValue = m_out.fpToInt32(value);
+        LValue integerValueConvertedToDouble = m_out.intToDouble(integerValue);
+        LValue valueNotConvertibleToInteger = m_out.doubleNotEqualOrUnordered(value, integerValueConvertedToDouble);
+        speculate(Overflow, FormattedValue(ValueFormatDouble, value), m_node, valueNotConvertibleToInteger);
+
+        if (shouldCheckNegativeZero) {
+            LBasicBlock valueIsZero = FTL_NEW_BLOCK(m_out, ("ConvertDoubleToInt32 on zero"));
+            LBasicBlock continuation = FTL_NEW_BLOCK(m_out, ("ConvertDoubleToInt32 continuation"));
+            m_out.branch(m_out.isZero32(integerValue), unsure(valueIsZero), unsure(continuation));
+
+            LBasicBlock lastNext = m_out.appendTo(valueIsZero, continuation);
+
+            LValue doubleBitcastToInt64 = m_out.bitCast(value, m_out.int64);
+            LValue signBitSet = m_out.lessThan(doubleBitcastToInt64, m_out.constInt64(0));
+
+            speculate(NegativeZero, FormattedValue(ValueFormatDouble, value), m_node, signBitSet);
+            m_out.jump(continuation);
+            m_out.appendTo(continuation, lastNext);
+        }
+        return integerValue;
+    }
+
     LValue isNumber(LValue jsValue, SpeculatedType type = SpecFullTop)
     {
         if (LValue proven = isProvenValue(type, SpecFullNumber))
index cd4b25a..e2d973d 100644 (file)
@@ -139,6 +139,10 @@ public:
     
     LValue insertElement(LValue vector, LValue element, LValue index) { return buildInsertElement(m_builder, vector, element, index); }
 
+    LValue ceil64(LValue operand)
+    {
+        return call(ceil64Intrinsic(), operand);
+    }
     LValue ctlz32(LValue xOperand, LValue yOperand)
     {
         return call(ctlz32Intrinsic(), xOperand, yOperand);
index df7864b..b9d6198 100644 (file)
@@ -32,6 +32,7 @@
 #include "JSArray.h"
 #include "JSArrayIterator.h"
 #include "JSStack.h"
+#include "MathCommon.h"
 #include "MaxFrameExtentForSlowPathCall.h"
 #include "JSCInlines.h"
 #include "SpecializedThunkJIT.h"
@@ -683,16 +684,6 @@ MacroAssemblerCodeRef sqrtThunkGenerator(VM* vm)
 #define UnaryDoubleOpWrapper(function) function##Wrapper
 enum MathThunkCallingConvention { };
 typedef MathThunkCallingConvention(*MathThunk)(MathThunkCallingConvention);
-extern "C" {
-
-double jsRound(double) REFERENCED_FROM_ASM;
-double jsRound(double d)
-{
-    double integer = ceil(d);
-    return integer - (integer - d > 0.5);
-}
-
-}
 
 #if CPU(X86_64) && COMPILER(GCC) && (OS(DARWIN) || OS(LINUX))
 
index 20c13d6..5420466 100644 (file)
@@ -420,4 +420,12 @@ double JIT_OPERATION operationMathPow(double x, double y)
     return mathPowInternal(x, y);
 }
 
+extern "C" {
+double jsRound(double value)
+{
+    double integer = ceil(value);
+    return integer - (integer - value > 0.5);
+}
+}
+
 } // namespace JSC
index f7b0afc..840d863 100644 (file)
@@ -54,6 +54,10 @@ inline int clz32(uint32_t number)
 #endif
 }
 
+extern "C" {
+double JIT_OPERATION jsRound(double value) REFERENCED_FROM_ASM WTF_INTERNAL;
+}
+
 }
 
 #endif // MathCommon_h
index 8e7d632..d23e46b 100644 (file)
@@ -268,9 +268,7 @@ EncodedJSValue JSC_HOST_CALL mathProtoFuncRandom(ExecState* exec)
 
 EncodedJSValue JSC_HOST_CALL mathProtoFuncRound(ExecState* exec)
 {
-    double arg = exec->argument(0).toNumber(exec);
-    double integer = ceil(arg);
-    return JSValue::encode(jsNumber(integer - (integer - arg > 0.5)));
+    return JSValue::encode(jsNumber(jsRound(exec->argument(0).toNumber(exec))));
 }
 
 EncodedJSValue JSC_HOST_CALL mathProtoFuncSign(ExecState* exec)
diff --git a/Source/JavaScriptCore/tests/stress/math-round-basics.js b/Source/JavaScriptCore/tests/stress/math-round-basics.js
new file mode 100644 (file)
index 0000000..2e38c76
--- /dev/null
@@ -0,0 +1,257 @@
+
+function mathRoundOnIntegers(value)
+{
+    return Math.round(value);
+}
+noInline(mathRoundOnIntegers);
+
+function mathRoundOnDoubles(value)
+{
+    return Math.round(value);
+}
+noInline(mathRoundOnDoubles);
+
+function mathRoundOnBooleans(value)
+{
+    return Math.round(value);
+}
+noInline(mathRoundOnBooleans);
+
+// The trivial cases first.
+for (var i = 1; i < 1e4; ++i) {
+    var roundedValue = mathRoundOnIntegers(i);
+    if (roundedValue !== i)
+        throw "mathRoundOnIntegers(" + i + ") = " + roundedValue;
+
+    var roundedValue = mathRoundOnIntegers(-i);
+    if (roundedValue !== -i)
+        throw "mathRoundOnIntegers(" + -i + ") = " + roundedValue;
+
+    var doubleLow = i + 0.4;
+    var roundedValue = mathRoundOnDoubles(doubleLow);
+    if (roundedValue !== i)
+        throw "mathRoundOnDoubles(" + doubleLow + ") = " + roundedValue;
+
+    var doubleHigh = i + 0.6;
+    var roundedValue = mathRoundOnDoubles(doubleHigh);
+    if (roundedValue !== i + 1)
+        throw "mathRoundOnDoubles(" + doubleHigh + ") = " + roundedValue;
+
+    var doubleMid = i + 0.5;
+    var roundedValue = mathRoundOnDoubles(doubleMid);
+    if (roundedValue !== i + 1)
+        throw "mathRoundOnDoubles(" + doubleMid + ") = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(-0.6);
+    if (roundedValue !== -1)
+        throw "mathRoundOnDoubles(-0.6) = " + roundedValue;
+}
+
+// Some more interesting cases, some of them well OSR exit when the return value is zero.
+for (var i = 0; i < 1e4; ++i) {
+    var roundedValue = mathRoundOnIntegers(i);
+    if (roundedValue !== i)
+        throw "mathRoundOnIntegers(" + i + ") = " + roundedValue;
+
+    var roundedValue = mathRoundOnIntegers(-i);
+    if (roundedValue !== -i)
+        throw "mathRoundOnIntegers(-" + i + ") = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(-0.4);
+    if (roundedValue !== 0)
+        throw "mathRoundOnDoubles(-0.4) = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(-0.5);
+    if (roundedValue !== 0)
+        throw "mathRoundOnDoubles(-0.5) = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(-0);
+    if (!(roundedValue === 0 && (1/roundedValue) === -Infinity))
+        throw "mathRoundOnDoubles(-0) = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(NaN);
+    if (roundedValue === roundedValue)
+        throw "mathRoundOnDoubles(NaN) = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(Number.POSITIVE_INFINITY);
+    if (roundedValue !== Number.POSITIVE_INFINITY)
+        throw "mathRoundOnDoubles(Number.POSITIVE_INFINITY) = " + roundedValue;
+
+    var roundedValue = mathRoundOnDoubles(Number.NEGATIVE_INFINITY);
+    if (roundedValue !== Number.NEGATIVE_INFINITY)
+        throw "mathRoundOnDoubles(Number.NEGATIVE_INFINITY) = " + roundedValue;
+
+    var boolean = !!(i % 2);
+    var roundedBoolean = mathRoundOnBooleans(boolean);
+    if (roundedBoolean != boolean)
+        throw "mathRoundOnDoubles(" + boolean + ") = " + roundedBoolean;
+}
+
+function uselessMathRound(value)
+{
+    return Math.round(value|0);
+}
+noInline(uselessMathRound);
+
+for (var i = 0; i < 1e4; ++i) {
+    var roundedValue = uselessMathRound(i);
+    if (roundedValue !== i)
+        throw "uselessMathRound(" + i + ") = " + roundedValue;
+
+    var doubleLow = i + 0.4;
+    var roundedValue = uselessMathRound(doubleLow);
+    if (roundedValue !== i)
+        throw "uselessMathRound(" + doubleLow + ") = " + roundedValue;
+
+    var doubleHigh = i + 0.6;
+    var roundedValue = uselessMathRound(doubleHigh);
+    if (roundedValue !== i)
+        throw "uselessMathRound(" + doubleHigh + ") = " + roundedValue;
+
+    var doubleMid = i + 0.5;
+    var roundedValue = uselessMathRound(doubleMid);
+    if (roundedValue !== i)
+        throw "uselessMathRound(" + doubleMid + ") = " + roundedValue;
+
+    var roundedValue = uselessMathRound(-0.4);
+    if (roundedValue !== 0)
+        throw "uselessMathRound(-0.4) = " + roundedValue;
+
+    var roundedValue = uselessMathRound(-0.5);
+    if (roundedValue !== 0)
+        throw "uselessMathRound(-0.5) = " + roundedValue;
+
+    var roundedValue = uselessMathRound(-0.6);
+    if (roundedValue !== 0)
+        throw "uselessMathRound(-0.6) = " + roundedValue;
+}
+
+function mathRoundWithOverflow(value)
+{
+    return Math.round(value);
+}
+noInline(mathRoundWithOverflow);
+
+for (var i = 0; i < 1e4; ++i) {
+    var bigValue = 1000000000000;
+    var roundedValue = mathRoundWithOverflow(bigValue);
+    if (roundedValue !== bigValue)
+        throw "mathRoundWithOverflow(" + bigValue + ") = " + roundedValue;
+}
+
+function mathRoundConsumedAsDouble(value)
+{
+    return Math.round(value) * 0.5;
+}
+noInline(mathRoundConsumedAsDouble);
+
+for (var i = 0; i < 1e4; ++i) {
+    var doubleValue = i + 0.1;
+    var roundedValue = mathRoundConsumedAsDouble(doubleValue);
+    if (roundedValue !== (i * 0.5))
+        throw "mathRoundConsumedAsDouble(" + doubleValue + ") = " + roundedValue;
+
+    var doubleValue = i + 0.6;
+    var roundedValue = mathRoundConsumedAsDouble(doubleValue);
+    if (roundedValue !== ((i + 1) * 0.5))
+        throw "mathRoundConsumedAsDouble(" + doubleValue + ") = " + roundedValue;
+
+}
+
+function mathRoundDoesNotCareAboutMinusZero(value)
+{
+    return Math.round(value)|0;
+}
+noInline(mathRoundDoesNotCareAboutMinusZero);
+
+for (var i = 0; i < 1e4; ++i) {
+    var doubleMid = i + 0.5;
+    var roundedValue = mathRoundDoesNotCareAboutMinusZero(doubleMid);
+    if (roundedValue !== i + 1)
+        throw "mathRoundDoesNotCareAboutMinusZero(" + doubleMid + ") = " + roundedValue;
+}
+
+
+// *** Function arguments. ***
+function mathRoundNoArguments()
+{
+    return Math.round();
+}
+noInline(mathRoundNoArguments);
+
+function mathRoundTooManyArguments(a, b, c)
+{
+    return Math.round(a, b, c);
+}
+noInline(mathRoundTooManyArguments);
+
+for (var i = 0; i < 1e4; ++i) {
+    var value = mathRoundNoArguments();
+    if (value === value)
+        throw "mathRoundNoArguments() = " + value;
+
+    var value = mathRoundTooManyArguments(2.1, 3, 5);
+    if (value !== 2)
+        throw "mathRoundTooManyArguments() = " + value;
+}
+
+
+// *** Constant as arguments. ***
+function testMathRoundOnConstants()
+{
+    var value = Math.round(0);
+    if (value !== 0)
+        throw "Math.round(0) = " + value;
+    var value = Math.round(-0);
+    if (!(value === 0 && (1/value) === -Infinity))
+        throw "Math.round(-0) = " + value;
+    var value = Math.round(1);
+    if (value !== 1)
+        throw "Math.round(1) = " + value;
+    var value = Math.round(-1);
+    if (value !== -1)
+        throw "Math.round(-1) = " + value;
+    var value = Math.round(42);
+    if (value !== 42)
+        throw "Math.round(42) = " + value;
+    var value = Math.round(-42.2);
+    if (value !== -42)
+        throw "Math.round(-42.2) = " + value;
+    var value = Math.round(NaN);
+    if (value === value)
+        throw "Math.round(NaN) = " + value;
+    var value = Math.round(Number.POSITIVE_INFINITI);
+    if (value === value)
+        throw "Math.round(Number.POSITIVE_INFINITI) = " + value;
+    var value = Math.round(Number.NEGATIVE_INFINITI);
+    if (value === value)
+        throw "Math.round(Number.NEGATIVE_INFINITI) = " + value;
+    var value = Math.round(Math.E);
+    if (value !== 3)
+        throw "Math.round(Math.E) = " + value;
+}
+noInline(testMathRoundOnConstants);
+
+for (var i = 0; i < 1e4; ++i) {
+    testMathRoundOnConstants();
+}
+
+
+// *** Struct transition. ***
+function mathRoundStructTransition(value)
+{
+    return Math.round(value);
+}
+noInline(mathRoundStructTransition);
+
+for (var i = 0; i < 1e4; ++i) {
+    var value = mathRoundStructTransition(42.5);
+    if (value !== 43)
+        throw "mathRoundStructTransition(42.5) = " + value;
+}
+
+Math.round = function() { return arguments[0] + 5; }
+
+var value = mathRoundStructTransition(42);
+if (value !== 47)
+    throw "mathRoundStructTransition(42) after transition = " + value;