Title: [122167] trunk
Revision
122167
Author
[email protected]
Date
2012-07-09 16:28:53 -0700 (Mon, 09 Jul 2012)

Log Message

Source/_javascript_Core: DFG may get stuck in an infinite fix point if it constant folds a mispredicted node
https://bugs.webkit.org/show_bug.cgi?id=90829
<rdar://problem/11823843>

Reviewed by Oliver Hunt.
        
If a node is shown to have been mispredicted during CFA, then don't allow constant
folding to make the graph even more degenerate. Instead, pull back on constant folding
and allow the normal OSR machinery to fix our profiling so that a future recompilation
doesn't see the same mistake.

* dfg/DFGAbstractState.cpp:
(JSC::DFG::AbstractState::execute):
* dfg/DFGAbstractState.h:
(JSC::DFG::AbstractState::trySetConstant):
(AbstractState):
* dfg/DFGPhase.h:
(JSC::DFG::Phase::name):
(Phase):
(JSC::DFG::runAndLog):
(DFG):
(JSC::DFG::runPhase):

LayoutTests: DFG may get stuck in an infinite fix point if it constant folds a mispredicted node
https://bugs.webkit.org/show_bug.cgi?id=90829

Reviewed by Oliver Hunt.

* fast/js/dfg-constant-fold-misprediction-expected.txt: Added.
* fast/js/dfg-constant-fold-misprediction.html: Added.
* fast/js/script-tests/dfg-constant-fold-misprediction.js: Added.
(foo):

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (122166 => 122167)


--- trunk/LayoutTests/ChangeLog	2012-07-09 23:26:54 UTC (rev 122166)
+++ trunk/LayoutTests/ChangeLog	2012-07-09 23:28:53 UTC (rev 122167)
@@ -1,5 +1,17 @@
 2012-07-09  Filip Pizlo  <[email protected]>
 
+        DFG may get stuck in an infinite fix point if it constant folds a mispredicted node
+        https://bugs.webkit.org/show_bug.cgi?id=90829
+
+        Reviewed by Oliver Hunt.
+
+        * fast/js/dfg-constant-fold-misprediction-expected.txt: Added.
+        * fast/js/dfg-constant-fold-misprediction.html: Added.
+        * fast/js/script-tests/dfg-constant-fold-misprediction.js: Added.
+        (foo):
+
+2012-07-09  Filip Pizlo  <[email protected]>
+
         fast/js/global-constructors.html is flaky and mostly useless
         https://bugs.webkit.org/show_bug.cgi?id=90833
 

Added: trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction-expected.txt (0 => 122167)


--- trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction-expected.txt	2012-07-09 23:28:53 UTC (rev 122167)
@@ -0,0 +1,14 @@
+This tests that a constant folding on a node that has obviously mispredicted type doesn't send the compiler into an infinite loop.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS foo(0.5) is 1000.50025
+PASS foo(0.5) is 1000.50025
+PASS foo(0.5) is 1000.50025
+PASS foo(0.5) is 1000.50025
+PASS foo(0.5) is 1000.50025
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction.html (0 => 122167)


--- trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction.html	                        (rev 0)
+++ trunk/LayoutTests/fast/js/dfg-constant-fold-misprediction.html	2012-07-09 23:28:53 UTC (rev 122167)
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<script src=""
+<script src=""
+</body>
+</html>

Added: trunk/LayoutTests/fast/js/script-tests/dfg-constant-fold-misprediction.js (0 => 122167)


--- trunk/LayoutTests/fast/js/script-tests/dfg-constant-fold-misprediction.js	                        (rev 0)
+++ trunk/LayoutTests/fast/js/script-tests/dfg-constant-fold-misprediction.js	2012-07-09 23:28:53 UTC (rev 122167)
@@ -0,0 +1,37 @@
+description(
+"This tests that a constant folding on a node that has obviously mispredicted type doesn't send the compiler into an infinite loop."
+);
+
+// A function with an argument correctly predicted double.
+function foo(x) {
+    // Two variables holding constants such that the bytecode generation constant folder
+    // will not constant fold the division below, but the DFG constant folder will.
+    var a = 1;
+    var b = 4000;
+    // A division that is going to be predicted integer on the first compilation. The
+    // compilation will be triggered from the loop below so the slow case counter of the
+    // division will be 1, which is too low for the division to be predicted double.
+    // If we constant fold this division, we'll have a constant node that is predicted
+    // integer but that contains a double. The subsequent addition to x, which is
+    // predicted double, will lead the Fixup phase to inject an Int32ToDouble node on
+    // the constant-that-was-a-division; subsequent fases in the fixpoint will constant
+    // fold that Int32ToDouble. And hence we will have an infinite loop. The correct fix
+    // is to disable constant folding of mispredicted nodes; that allows the normal
+    // process of correcting predictions (OSR exit profiling, exiting to profiled code,
+    // and recompilation with exponential backoff) to take effect so that the next
+    // compilation does not make this same mistake.
+    var c = (a / b) + x;
+    // A pointless loop to force the first compilation to occur before the division got
+    // hot. If this loop was not here then the division would be known to produce doubles
+    // on the first compilation.
+    var d = 0;
+    for (var i = 0; i < 1000; ++i)
+        d++;
+    return c + d;
+}
+
+// Call foo() enough times to make totally sure that we optimize.
+for (var i = 0; i < 5; ++i)
+    shouldBe("foo(0.5)", "1000.50025");
+
+

Modified: trunk/Source/_javascript_Core/ChangeLog (122166 => 122167)


--- trunk/Source/_javascript_Core/ChangeLog	2012-07-09 23:26:54 UTC (rev 122166)
+++ trunk/Source/_javascript_Core/ChangeLog	2012-07-09 23:28:53 UTC (rev 122167)
@@ -1,5 +1,30 @@
 2012-07-09  Filip Pizlo  <[email protected]>
 
+        DFG may get stuck in an infinite fix point if it constant folds a mispredicted node
+        https://bugs.webkit.org/show_bug.cgi?id=90829
+        <rdar://problem/11823843>
+
+        Reviewed by Oliver Hunt.
+        
+        If a node is shown to have been mispredicted during CFA, then don't allow constant
+        folding to make the graph even more degenerate. Instead, pull back on constant folding
+        and allow the normal OSR machinery to fix our profiling so that a future recompilation
+        doesn't see the same mistake.
+
+        * dfg/DFGAbstractState.cpp:
+        (JSC::DFG::AbstractState::execute):
+        * dfg/DFGAbstractState.h:
+        (JSC::DFG::AbstractState::trySetConstant):
+        (AbstractState):
+        * dfg/DFGPhase.h:
+        (JSC::DFG::Phase::name):
+        (Phase):
+        (JSC::DFG::runAndLog):
+        (DFG):
+        (JSC::DFG::runPhase):
+
+2012-07-09  Filip Pizlo  <[email protected]>
+
         It should be possible to jettison JIT stub routines even if they are currently running
         https://bugs.webkit.org/show_bug.cgi?id=90731
 

Modified: trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp (122166 => 122167)


--- trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp	2012-07-09 23:26:54 UTC (rev 122166)
+++ trunk/Source/_javascript_Core/dfg/DFGAbstractState.cpp	2012-07-09 23:28:53 UTC (rev 122167)
@@ -311,31 +311,35 @@
         if (left && right && left.isInt32() && right.isInt32()) {
             int32_t a = left.asInt32();
             int32_t b = right.asInt32();
+            bool constantWasSet;
             switch (node.op()) {
             case BitAnd:
-                forNode(nodeIndex).set(JSValue(a & b));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a & b));
                 break;
             case BitOr:
-                forNode(nodeIndex).set(JSValue(a | b));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a | b));
                 break;
             case BitXor:
-                forNode(nodeIndex).set(JSValue(a ^ b));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a ^ b));
                 break;
             case BitRShift:
-                forNode(nodeIndex).set(JSValue(a >> static_cast<uint32_t>(b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a >> static_cast<uint32_t>(b)));
                 break;
             case BitLShift:
-                forNode(nodeIndex).set(JSValue(a << static_cast<uint32_t>(b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a << static_cast<uint32_t>(b)));
                 break;
             case BitURShift:
-                forNode(nodeIndex).set(JSValue(static_cast<uint32_t>(a) >> static_cast<uint32_t>(b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(static_cast<uint32_t>(a) >> static_cast<uint32_t>(b)));
                 break;
             default:
                 ASSERT_NOT_REACHED();
+                constantWasSet = false;
             }
-            m_foundConstants = true;
-            node.setCanExit(false);
-            break;
+            if (constantWasSet) {
+                m_foundConstants = true;
+                node.setCanExit(false);
+                break;
+            }
         }
         speculateInt32Binary(node);
         forNode(nodeIndex).set(SpecInt32);
@@ -346,10 +350,11 @@
         JSValue child = forNode(node.child1()).value();
         if (child && child.isNumber()) {
             ASSERT(child.isInt32());
-            forNode(nodeIndex).set(JSValue(child.asUInt32()));
-            m_foundConstants = true;
-            node.setCanExit(false);
-            break;
+            if (trySetConstant(nodeIndex, JSValue(child.asUInt32()))) {
+                m_foundConstants = true;
+                node.setCanExit(false);
+                break;
+            }
         }
         if (!node.canSpeculateInteger()) {
             forNode(nodeIndex).set(SpecDouble);
@@ -367,8 +372,8 @@
         if (child && child.isNumber()) {
             double asDouble = child.asNumber();
             int32_t asInt = JSC::toInt32(asDouble);
-            if (bitwise_cast<int64_t>(static_cast<double>(asInt)) == bitwise_cast<int64_t>(asDouble)) {
-                forNode(nodeIndex).set(JSValue(asInt));
+            if (bitwise_cast<int64_t>(static_cast<double>(asInt)) == bitwise_cast<int64_t>(asDouble)
+                && trySetConstant(nodeIndex, JSValue(asInt))) {
                 m_foundConstants = true;
                 break;
             }
@@ -382,13 +387,16 @@
     case ValueToInt32: {
         JSValue child = forNode(node.child1()).value();
         if (child && child.isNumber()) {
+            bool constantWasSet;
             if (child.isInt32())
-                forNode(nodeIndex).set(child);
+                constantWasSet = trySetConstant(nodeIndex, child);
             else
-                forNode(nodeIndex).set(JSValue(JSC::toInt32(child.asDouble())));
-            m_foundConstants = true;
-            node.setCanExit(false);
-            break;
+                constantWasSet = trySetConstant(nodeIndex, JSValue(JSC::toInt32(child.asDouble())));
+            if (constantWasSet) {
+                m_foundConstants = true;
+                node.setCanExit(false);
+                break;
+            }
         }
         if (m_graph[node.child1()].shouldSpeculateInteger())
             speculateInt32Unary(node);
@@ -405,8 +413,8 @@
         
     case Int32ToDouble: {
         JSValue child = forNode(node.child1()).value();
-        if (child && child.isNumber()) {
-            forNode(nodeIndex).set(JSValue(JSValue::EncodeAsDouble, child.asNumber()));
+        if (child && child.isNumber()
+            && trySetConstant(nodeIndex, JSValue(JSValue::EncodeAsDouble, child.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -424,8 +432,8 @@
     case ArithAdd: {
         JSValue left = forNode(node.child1()).value();
         JSValue right = forNode(node.child2()).value();
-        if (left && right && left.isNumber() && right.isNumber()) {
-            forNode(nodeIndex).set(JSValue(left.asNumber() + right.asNumber()));
+        if (left && right && left.isNumber() && right.isNumber()
+            && trySetConstant(nodeIndex, JSValue(left.asNumber() + right.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -456,8 +464,8 @@
     case ArithSub: {
         JSValue left = forNode(node.child1()).value();
         JSValue right = forNode(node.child2()).value();
-        if (left && right && left.isNumber() && right.isNumber()) {
-            forNode(nodeIndex).set(JSValue(left.asNumber() - right.asNumber()));
+        if (left && right && left.isNumber() && right.isNumber()
+            && trySetConstant(nodeIndex, JSValue(left.asNumber() - right.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -475,8 +483,8 @@
         
     case ArithNegate: {
         JSValue child = forNode(node.child1()).value();
-        if (child && child.isNumber()) {
-            forNode(nodeIndex).set(JSValue(-child.asNumber()));
+        if (child && child.isNumber()
+            && trySetConstant(nodeIndex, JSValue(-child.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -495,8 +503,8 @@
     case ArithMul: {
         JSValue left = forNode(node.child1()).value();
         JSValue right = forNode(node.child2()).value();
-        if (left && right && left.isNumber() && right.isNumber()) {
-            forNode(nodeIndex).set(JSValue(left.asNumber() * right.asNumber()));
+        if (left && right && left.isNumber() && right.isNumber()
+            && trySetConstant(nodeIndex, JSValue(left.asNumber() * right.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -523,26 +531,30 @@
         if (left && right && left.isNumber() && right.isNumber()) {
             double a = left.asNumber();
             double b = right.asNumber();
+            bool constantWasSet;
             switch (node.op()) {
             case ArithDiv:
-                forNode(nodeIndex).set(JSValue(a / b));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a / b));
                 break;
             case ArithMin:
-                forNode(nodeIndex).set(JSValue(a < b ? a : (b <= a ? b : a + b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a < b ? a : (b <= a ? b : a + b)));
                 break;
             case ArithMax:
-                forNode(nodeIndex).set(JSValue(a > b ? a : (b >= a ? b : a + b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(a > b ? a : (b >= a ? b : a + b)));
                 break;
             case ArithMod:
-                forNode(nodeIndex).set(JSValue(fmod(a, b)));
+                constantWasSet = trySetConstant(nodeIndex, JSValue(fmod(a, b)));
                 break;
             default:
                 ASSERT_NOT_REACHED();
+                constantWasSet = false;
                 break;
             }
-            m_foundConstants = true;
-            node.setCanExit(false);
-            break;
+            if (constantWasSet) {
+                m_foundConstants = true;
+                node.setCanExit(false);
+                break;
+            }
         }
         if (Node::shouldSpeculateInteger(
                 m_graph[node.child1()], m_graph[node.child2()])
@@ -558,8 +570,8 @@
             
     case ArithAbs: {
         JSValue child = forNode(node.child1()).value();
-        if (child && child.isNumber()) {
-            forNode(nodeIndex).set(JSValue(fabs(child.asNumber())));
+        if (child && child.isNumber()
+            && trySetConstant(nodeIndex, JSValue(fabs(child.asNumber())))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -577,8 +589,8 @@
             
     case ArithSqrt: {
         JSValue child = forNode(node.child1()).value();
-        if (child && child.isNumber()) {
-            forNode(nodeIndex).set(JSValue(sqrt(child.asNumber())));
+        if (child && child.isNumber()
+            && trySetConstant(nodeIndex, JSValue(sqrt(child.asNumber())))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -590,8 +602,7 @@
             
     case LogicalNot: {
         JSValue childConst = forNode(node.child1()).value();
-        if (childConst) {
-            forNode(nodeIndex).set(jsBoolean(!childConst.toBoolean()));
+        if (childConst && trySetConstant(nodeIndex, jsBoolean(!childConst.toBoolean()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -626,27 +637,28 @@
         node.setCanExit(false);
         JSValue child = forNode(node.child1()).value();
         if (child) {
-            bool foundConstant = true;
+            bool constantWasSet;
             switch (node.op()) {
             case IsUndefined:
-                forNode(nodeIndex).set(jsBoolean(
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(
                     child.isCell()
                     ? child.asCell()->structure()->typeInfo().masqueradesAsUndefined()
                     : child.isUndefined()));
                 break;
             case IsBoolean:
-                forNode(nodeIndex).set(jsBoolean(child.isBoolean()));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(child.isBoolean()));
                 break;
             case IsNumber:
-                forNode(nodeIndex).set(jsBoolean(child.isNumber()));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(child.isNumber()));
                 break;
             case IsString:
-                forNode(nodeIndex).set(jsBoolean(isJSString(child)));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(isJSString(child)));
                 break;
             default:
+                constantWasSet = false;
                 break;
             }
-            if (foundConstant) {
+            if (constantWasSet) {
                 m_foundConstants = true;
                 break;
             }
@@ -665,29 +677,33 @@
         if (leftConst && rightConst && leftConst.isNumber() && rightConst.isNumber()) {
             double a = leftConst.asNumber();
             double b = rightConst.asNumber();
+            bool constantWasSet;
             switch (node.op()) {
             case CompareLess:
-                forNode(nodeIndex).set(jsBoolean(a < b));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(a < b));
                 break;
             case CompareLessEq:
-                forNode(nodeIndex).set(jsBoolean(a <= b));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(a <= b));
                 break;
             case CompareGreater:
-                forNode(nodeIndex).set(jsBoolean(a > b));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(a > b));
                 break;
             case CompareGreaterEq:
-                forNode(nodeIndex).set(jsBoolean(a >= b));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(a >= b));
                 break;
             case CompareEq:
-                forNode(nodeIndex).set(jsBoolean(a == b));
+                constantWasSet = trySetConstant(nodeIndex, jsBoolean(a == b));
                 break;
             default:
                 ASSERT_NOT_REACHED();
+                constantWasSet = false;
                 break;
             }
-            m_foundConstants = true;
-            node.setCanExit(false);
-            break;
+            if (constantWasSet) {
+                m_foundConstants = true;
+                node.setCanExit(false);
+                break;
+            }
         }
         
         forNode(nodeIndex).set(SpecBoolean);
@@ -767,8 +783,8 @@
     case CompareStrictEq: {
         JSValue left = forNode(node.child1()).value();
         JSValue right = forNode(node.child2()).value();
-        if (left && right && left.isNumber() && right.isNumber()) {
-            forNode(nodeIndex).set(jsBoolean(left.asNumber() == right.asNumber()));
+        if (left && right && left.isNumber() && right.isNumber()
+            && trySetConstant(nodeIndex, jsBoolean(left.asNumber() == right.asNumber()))) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;
@@ -1106,8 +1122,7 @@
             
     case ToPrimitive: {
         JSValue childConst = forNode(node.child1()).value();
-        if (childConst && childConst.isNumber()) {
-            forNode(nodeIndex).set(childConst);
+        if (childConst && childConst.isNumber() && trySetConstant(nodeIndex, childConst)) {
             m_foundConstants = true;
             node.setCanExit(false);
             break;

Modified: trunk/Source/_javascript_Core/dfg/DFGAbstractState.h (122166 => 122167)


--- trunk/Source/_javascript_Core/dfg/DFGAbstractState.h	2012-07-09 23:26:54 UTC (rev 122166)
+++ trunk/Source/_javascript_Core/dfg/DFGAbstractState.h	2012-07-09 23:28:53 UTC (rev 122167)
@@ -267,6 +267,23 @@
         childValue2.filter(SpecNumber);
     }
     
+    bool trySetConstant(NodeIndex nodeIndex, JSValue value)
+    {
+        // Make sure we don't constant fold something that will produce values that contravene
+        // predictions. If that happens then we know that the code will OSR exit, forcing
+        // recompilation. But if we tried to constant fold then we'll have a very degenerate
+        // IR: namely we'll have a JSConstant that contravenes its own prediction. There's a
+        // lot of subtle code that assumes that
+        // speculationFromValue(jsConstant) == jsConstant.prediction(). "Hardening" that code
+        // is probably less sane than just pulling back on constant folding.
+        SpeculatedType oldType = m_graph[nodeIndex].prediction();
+        if (mergeSpeculations(speculationFromValue(value), oldType) != oldType)
+            return false;
+        
+        forNode(nodeIndex).set(value);
+        return true;
+    }
+    
     CodeBlock* m_codeBlock;
     Graph& m_graph;
     

Modified: trunk/Source/_javascript_Core/dfg/DFGPhase.h (122166 => 122167)


--- trunk/Source/_javascript_Core/dfg/DFGPhase.h	2012-07-09 23:26:54 UTC (rev 122166)
+++ trunk/Source/_javascript_Core/dfg/DFGPhase.h	2012-07-09 23:28:53 UTC (rev 122167)
@@ -49,6 +49,8 @@
         endPhase();
     }
     
+    const char* name() const { return m_name; }
+    
     // Each phase must have a run() method.
     
 protected:
@@ -76,17 +78,28 @@
 };
 
 template<typename PhaseType>
+bool runAndLog(PhaseType& phase)
+{
+    bool result = phase.run();
+#if DFG_ENABLE(DEBUG_VERBOSE)
+    if (result)
+        dataLog("Phase %s changed the IR.\n", phase.name());
+#endif
+    return result;
+}
+
+template<typename PhaseType>
 bool runPhase(Graph& graph)
 {
     PhaseType phase(graph);
-    return phase.run();
+    return runAndLog(phase);
 }
 
 template<typename PhaseType, typename ArgumentType1>
 bool runPhase(Graph& graph, ArgumentType1 arg1)
 {
     PhaseType phase(graph, arg1);
-    return phase.run();
+    return runAndLog(phase);
 }
 
 } } // namespace JSC::DFG
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to