Title: [197492] trunk/Source/_javascript_Core
Revision
197492
Author
[email protected]
Date
2016-03-02 21:58:59 -0800 (Wed, 02 Mar 2016)

Log Message

RegExpExec/RegExpTest should not unconditionally speculate cell
https://bugs.webkit.org/show_bug.cgi?id=154901

Reviewed by Benjamin Poulain.

This is a three part change. It all started with a simple goal: end the rage-recompiles in
Octane/regexp by enabling the DFG and FTL to do untyped RegExpExec/RegExpTest. This keeps us
in the optimized code when you do a regexp match on a number, for example.

While implementing this, I realized that DFGOperations.cpp was bad at exception checking. When
it did check for exceptions, it used exec->hadException() instead of vm.exception(). So I
fixed that. I also made sure that the regexp operations checked for exception after doing
toString().

Unfortunately, the introduction of untyped RegExpExec/RegExpTest caused a regression on
Octane/regexp. This was because we were simultaneously scheduling replacement and OSR compiles
of some large functions with the FTL JIT. The OSR compiles were not useful. This was a
regression from the previous changes to make OSR compiles happen sooner. The problem is that
this change also removed the throttling of OSR compiles even in those cases where we suspect
that replacement is more likely. This patch reintroduces that throttling, but only in the
replacement path.

This change ends up being neutral overall.

* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileRegExpExec):
(JSC::FTL::DFG::LowerDFGToB3::compileRegExpTest):
(JSC::FTL::DFG::LowerDFGToB3::compileNewRegexp):
* tests/stress/regexp-exec-effect-after-exception.js: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/Source/_javascript_Core/ChangeLog (197491 => 197492)


--- trunk/Source/_javascript_Core/ChangeLog	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/ChangeLog	2016-03-03 05:58:59 UTC (rev 197492)
@@ -1,3 +1,43 @@
+2016-03-02  Filip Pizlo  <[email protected]>
+
+        RegExpExec/RegExpTest should not unconditionally speculate cell
+        https://bugs.webkit.org/show_bug.cgi?id=154901
+
+        Reviewed by Benjamin Poulain.
+
+        This is a three part change. It all started with a simple goal: end the rage-recompiles in
+        Octane/regexp by enabling the DFG and FTL to do untyped RegExpExec/RegExpTest. This keeps us
+        in the optimized code when you do a regexp match on a number, for example.
+
+        While implementing this, I realized that DFGOperations.cpp was bad at exception checking. When
+        it did check for exceptions, it used exec->hadException() instead of vm.exception(). So I
+        fixed that. I also made sure that the regexp operations checked for exception after doing
+        toString().
+
+        Unfortunately, the introduction of untyped RegExpExec/RegExpTest caused a regression on
+        Octane/regexp. This was because we were simultaneously scheduling replacement and OSR compiles
+        of some large functions with the FTL JIT. The OSR compiles were not useful. This was a
+        regression from the previous changes to make OSR compiles happen sooner. The problem is that
+        this change also removed the throttling of OSR compiles even in those cases where we suspect
+        that replacement is more likely. This patch reintroduces that throttling, but only in the
+        replacement path.
+
+        This change ends up being neutral overall.
+
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileRegExpExec):
+        (JSC::FTL::DFG::LowerDFGToB3::compileRegExpTest):
+        (JSC::FTL::DFG::LowerDFGToB3::compileNewRegexp):
+        * tests/stress/regexp-exec-effect-after-exception.js: Added.
+
 2016-03-02  Benjamin Poulain  <[email protected]>
 
         [JSC] JSCell_freeListNext and JSCell_structureID are considered not overlapping

Modified: trunk/Source/_javascript_Core/dfg/DFGFixupPhase.cpp (197491 => 197492)


--- trunk/Source/_javascript_Core/dfg/DFGFixupPhase.cpp	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/dfg/DFGFixupPhase.cpp	2016-03-03 05:58:59 UTC (rev 197492)
@@ -879,8 +879,14 @@
             
         case RegExpExec:
         case RegExpTest: {
-            fixEdge<CellUse>(node->child1());
-            fixEdge<CellUse>(node->child2());
+            // FIXME: These should probably speculate something stronger than cell.
+            // https://bugs.webkit.org/show_bug.cgi?id=154900
+            if (node->child1()->shouldSpeculateCell()
+                && node->child2()->shouldSpeculateCell()) {
+                fixEdge<CellUse>(node->child1());
+                fixEdge<CellUse>(node->child2());
+                break;
+            }
             break;
         }
 

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.cpp (197491 => 197492)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2016-03-03 05:58:59 UTC (rev 197492)
@@ -361,10 +361,10 @@
     }
 
     baseValue.requireObjectCoercible(exec);
-    if (exec->hadException())
+    if (vm.exception())
         return JSValue::encode(jsUndefined());
     auto propertyName = property.toPropertyKey(exec);
-    if (exec->hadException())
+    if (vm.exception())
         return JSValue::encode(jsUndefined());
     return JSValue::encode(baseValue.get(exec, propertyName));
 }
@@ -394,7 +394,7 @@
     }
 
     auto propertyName = property.toPropertyKey(exec);
-    if (exec->hadException())
+    if (vm.exception())
         return JSValue::encode(jsUndefined());
     return JSValue::encode(JSValue(base).get(exec, propertyName));
 }
@@ -622,11 +622,34 @@
     if (!base->inherits(RegExpObject::info()))
         return throwVMTypeError(exec);
 
-    ASSERT(argument->isString() || argument->isObject());
-    JSString* input = argument->isString() ? asString(argument) : asObject(argument)->toString(exec);
+    JSString* input;
+    if (argument->isString())
+        input = asString(argument);
+    else {
+        input = JSValue(argument).toStringOrNull(exec);
+        if (!input)
+            return JSValue::encode(jsUndefined());
+    }
     return JSValue::encode(asRegExpObject(base)->exec(exec, input));
 }
         
+EncodedJSValue JIT_OPERATION operationRegExpExecGeneric(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedArgument)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    JSValue base = JSValue::decode(encodedBase);
+    JSValue argument = JSValue::decode(encodedArgument);
+    
+    if (!base.inherits(RegExpObject::info()))
+        return throwVMTypeError(exec);
+
+    JSString* input = argument.toStringOrNull(exec);
+    if (!input)
+        return JSValue::encode(jsUndefined());
+    return JSValue::encode(asRegExpObject(base)->exec(exec, input));
+}
+        
 size_t JIT_OPERATION operationRegExpTest(ExecState* exec, JSCell* base, JSCell* argument)
 {
     VM& vm = exec->vm();
@@ -637,11 +660,36 @@
         return false;
     }
 
-    ASSERT(argument->isString() || argument->isObject());
-    JSString* input = argument->isString() ? asString(argument) : asObject(argument)->toString(exec);
+    JSString* input;
+    if (argument->isString())
+        input = asString(argument);
+    else {
+        input = JSValue(argument).toStringOrNull(exec);
+        if (!input)
+            return false;
+    }
     return asRegExpObject(base)->test(exec, input);
 }
 
+size_t JIT_OPERATION operationRegExpTestGeneric(ExecState* exec, EncodedJSValue encodedBase, EncodedJSValue encodedArgument)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+
+    JSValue base = JSValue::decode(encodedBase);
+    JSValue argument = JSValue::decode(encodedArgument);
+
+    if (!base.inherits(RegExpObject::info())) {
+        throwTypeError(exec);
+        return false;
+    }
+
+    JSString* input = argument.toStringOrNull(exec);
+    if (!input)
+        return false;
+    return asRegExpObject(base)->test(exec, input);
+}
+
 size_t JIT_OPERATION operationCompareStrictEqCell(ExecState* exec, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2)
 {
     VM* vm = &exec->vm();
@@ -1182,9 +1230,9 @@
     NativeCallFrameTracer tracer(&vm, exec);
 
     JSString* str1 = JSValue::decode(a).toString(exec);
-    ASSERT(!exec->hadException()); // Impossible, since we must have been given primitives.
+    ASSERT(!vm.exception()); // Impossible, since we must have been given primitives.
     JSString* str2 = JSValue::decode(b).toString(exec);
-    ASSERT(!exec->hadException());
+    ASSERT(!vm.exception());
 
     if (sumOverflows<int32_t>(str1->length(), str2->length())) {
         throwOutOfMemoryError(exec);
@@ -1200,11 +1248,11 @@
     NativeCallFrameTracer tracer(&vm, exec);
 
     JSString* str1 = JSValue::decode(a).toString(exec);
-    ASSERT(!exec->hadException()); // Impossible, since we must have been given primitives.
+    ASSERT(!vm.exception()); // Impossible, since we must have been given primitives.
     JSString* str2 = JSValue::decode(b).toString(exec);
-    ASSERT(!exec->hadException());
+    ASSERT(!vm.exception());
     JSString* str3 = JSValue::decode(c).toString(exec);
-    ASSERT(!exec->hadException());
+    ASSERT(!vm.exception());
 
     if (sumOverflows<int32_t>(str1->length(), str2->length(), str3->length())) {
         throwOutOfMemoryError(exec);
@@ -1560,6 +1608,11 @@
 
         if (!codeBlock->hasOptimizedReplacement())
             return nullptr;
+
+        if (jitCode->osrEntryRetry < Options::ftlOSREntryRetryThreshold()) {
+            jitCode->osrEntryRetry++;
+            return nullptr;
+        }
     }
     
     // It's time to try to compile code for OSR entry.

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.h (197491 => 197492)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.h	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.h	2016-03-03 05:58:59 UTC (rev 197492)
@@ -102,8 +102,10 @@
 EncodedJSValue JIT_OPERATION operationArrayPop(ExecState*, JSArray*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationArrayPopAndRecoverLength(ExecState*, JSArray*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationRegExpExec(ExecState*, JSCell*, JSCell*) WTF_INTERNAL;
+EncodedJSValue JIT_OPERATION operationRegExpExecGeneric(ExecState*, EncodedJSValue, EncodedJSValue) WTF_INTERNAL;
 // These comparisons return a boolean within a size_t such that the value is zero extended to fill the register.
 size_t JIT_OPERATION operationRegExpTest(ExecState*, JSCell*, JSCell*) WTF_INTERNAL;
+size_t JIT_OPERATION operationRegExpTestGeneric(ExecState*, EncodedJSValue, EncodedJSValue) WTF_INTERNAL;
 size_t JIT_OPERATION operationCompareStrictEqCell(ExecState*, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) WTF_INTERNAL;
 size_t JIT_OPERATION operationCompareStrictEq(ExecState*, EncodedJSValue encodedOp1, EncodedJSValue encodedOp2) WTF_INTERNAL;
 JSCell* JIT_OPERATION operationCreateActivationDirect(ExecState*, Structure*, JSScope*, SymbolTable*, EncodedJSValue);

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp (197491 => 197492)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT32_64.cpp	2016-03-03 05:58:59 UTC (rev 197492)
@@ -2836,15 +2836,34 @@
     }
 
     case RegExpExec: {
-        SpeculateCellOperand base(this, node->child1());
-        SpeculateCellOperand argument(this, node->child2());
-        GPRReg baseGPR = base.gpr();
-        GPRReg argumentGPR = argument.gpr();
+        if (node->child1().useKind() == CellUse
+            && node->child2().useKind() == CellUse) {
+            SpeculateCellOperand base(this, node->child1());
+            SpeculateCellOperand argument(this, node->child2());
+            GPRReg baseGPR = base.gpr();
+            GPRReg argumentGPR = argument.gpr();
         
+            flushRegisters();
+            GPRFlushedCallResult2 resultTag(this);
+            GPRFlushedCallResult resultPayload(this);
+            callOperation(operationRegExpExec, resultTag.gpr(), resultPayload.gpr(), baseGPR, argumentGPR);
+            m_jit.exceptionCheck();
+        
+            jsValueResult(resultTag.gpr(), resultPayload.gpr(), node);
+            break;
+        }
+        
+        JSValueOperand base(this, node->child1());
+        JSValueOperand argument(this, node->child2());
+        GPRReg baseTagGPR = base.tagGPR();
+        GPRReg basePayloadGPR = base.payloadGPR();
+        GPRReg argumentTagGPR = argument.tagGPR();
+        GPRReg argumentPayloadGPR = argument.payloadGPR();
+        
         flushRegisters();
         GPRFlushedCallResult2 resultTag(this);
         GPRFlushedCallResult resultPayload(this);
-        callOperation(operationRegExpExec, resultTag.gpr(), resultPayload.gpr(), baseGPR, argumentGPR);
+        callOperation(operationRegExpExecGeneric, resultTag.gpr(), resultPayload.gpr(), baseTagGPR, basePayloadGPR, argumentTagGPR, argumentPayloadGPR);
         m_jit.exceptionCheck();
         
         jsValueResult(resultTag.gpr(), resultPayload.gpr(), node);
@@ -2852,17 +2871,34 @@
     }
         
     case RegExpTest: {
-        SpeculateCellOperand base(this, node->child1());
-        SpeculateCellOperand argument(this, node->child2());
-        GPRReg baseGPR = base.gpr();
-        GPRReg argumentGPR = argument.gpr();
+        if (node->child1().useKind() == CellUse
+            && node->child2().useKind() == CellUse) {
+            SpeculateCellOperand base(this, node->child1());
+            SpeculateCellOperand argument(this, node->child2());
+            GPRReg baseGPR = base.gpr();
+            GPRReg argumentGPR = argument.gpr();
         
+            flushRegisters();
+            GPRFlushedCallResult result(this);
+            callOperation(operationRegExpTest, result.gpr(), baseGPR, argumentGPR);
+            m_jit.exceptionCheck();
+        
+            booleanResult(result.gpr(), node);
+            break;
+        }
+        
+        JSValueOperand base(this, node->child1());
+        JSValueOperand argument(this, node->child2());
+        GPRReg baseTagGPR = base.tagGPR();
+        GPRReg basePayloadGPR = base.payloadGPR();
+        GPRReg argumentTagGPR = argument.tagGPR();
+        GPRReg argumentPayloadGPR = argument.payloadGPR();
+        
         flushRegisters();
         GPRFlushedCallResult result(this);
-        callOperation(operationRegExpTest, result.gpr(), baseGPR, argumentGPR);
+        callOperation(operationRegExpTestGeneric, result.gpr(), baseTagGPR, basePayloadGPR, argumentTagGPR, argumentPayloadGPR);
         m_jit.exceptionCheck();
         
-        // If we add a DataFormatBool, we should use it here.
         booleanResult(result.gpr(), node);
         break;
     }

Modified: trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp (197491 => 197492)


--- trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/dfg/DFGSpeculativeJIT64.cpp	2016-03-03 05:58:59 UTC (rev 197492)
@@ -2961,14 +2961,30 @@
     }
 
     case RegExpExec: {
-        SpeculateCellOperand base(this, node->child1());
-        SpeculateCellOperand argument(this, node->child2());
+        if (node->child1().useKind() == CellUse
+            && node->child2().useKind() == CellUse) {
+            SpeculateCellOperand base(this, node->child1());
+            SpeculateCellOperand argument(this, node->child2());
+            GPRReg baseGPR = base.gpr();
+            GPRReg argumentGPR = argument.gpr();
+        
+            flushRegisters();
+            GPRFlushedCallResult result(this);
+            callOperation(operationRegExpExec, result.gpr(), baseGPR, argumentGPR);
+            m_jit.exceptionCheck();
+        
+            jsValueResult(result.gpr(), node);
+            break;
+        }
+        
+        JSValueOperand base(this, node->child1());
+        JSValueOperand argument(this, node->child2());
         GPRReg baseGPR = base.gpr();
         GPRReg argumentGPR = argument.gpr();
         
         flushRegisters();
         GPRFlushedCallResult result(this);
-        callOperation(operationRegExpExec, result.gpr(), baseGPR, argumentGPR);
+        callOperation(operationRegExpExecGeneric, result.gpr(), baseGPR, argumentGPR);
         m_jit.exceptionCheck();
         
         jsValueResult(result.gpr(), node);
@@ -2976,14 +2992,32 @@
     }
 
     case RegExpTest: {
-        SpeculateCellOperand base(this, node->child1());
-        SpeculateCellOperand argument(this, node->child2());
+        if (node->child1().useKind() == CellUse
+            && node->child2().useKind() == CellUse) {
+            SpeculateCellOperand base(this, node->child1());
+            SpeculateCellOperand argument(this, node->child2());
+            GPRReg baseGPR = base.gpr();
+            GPRReg argumentGPR = argument.gpr();
+        
+            flushRegisters();
+            GPRFlushedCallResult result(this);
+            callOperation(operationRegExpTest, result.gpr(), baseGPR, argumentGPR);
+            m_jit.exceptionCheck();
+        
+            // If we add a DataFormatBool, we should use it here.
+            m_jit.or32(TrustedImm32(ValueFalse), result.gpr());
+            jsValueResult(result.gpr(), node, DataFormatJSBoolean);
+            break;
+        }
+        
+        JSValueOperand base(this, node->child1());
+        JSValueOperand argument(this, node->child2());
         GPRReg baseGPR = base.gpr();
         GPRReg argumentGPR = argument.gpr();
         
         flushRegisters();
         GPRFlushedCallResult result(this);
-        callOperation(operationRegExpTest, result.gpr(), baseGPR, argumentGPR);
+        callOperation(operationRegExpTestGeneric, result.gpr(), baseGPR, argumentGPR);
         m_jit.exceptionCheck();
         
         // If we add a DataFormatBool, we should use it here.

Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (197491 => 197492)


--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2016-03-03 05:29:16 UTC (rev 197491)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2016-03-03 05:58:59 UTC (rev 197492)
@@ -6434,18 +6434,36 @@
 
     void compileRegExpExec()
     {
-        LValue base = lowCell(m_node->child1());
-        LValue argument = lowCell(m_node->child2());
+        if (m_node->child1().useKind() == CellUse
+            && m_node->child2().useKind() == CellUse) {
+            LValue base = lowCell(m_node->child1());
+            LValue argument = lowCell(m_node->child2());
+            setJSValue(
+                vmCall(Int64, m_out.operation(operationRegExpExec), m_callFrame, base, argument));
+            return;
+        }
+        
+        LValue base = lowJSValue(m_node->child1());
+        LValue argument = lowJSValue(m_node->child2());
         setJSValue(
-            vmCall(Int64, m_out.operation(operationRegExpExec), m_callFrame, base, argument));
+            vmCall(Int64, m_out.operation(operationRegExpExecGeneric), m_callFrame, base, argument));
     }
 
     void compileRegExpTest()
     {
-        LValue base = lowCell(m_node->child1());
-        LValue argument = lowCell(m_node->child2());
+        if (m_node->child1().useKind() == CellUse
+            && m_node->child2().useKind() == CellUse) {
+            LValue base = lowCell(m_node->child1());
+            LValue argument = lowCell(m_node->child2());
+            setBoolean(
+                vmCall(Int32, m_out.operation(operationRegExpTest), m_callFrame, base, argument));
+            return;
+        }
+
+        LValue base = lowJSValue(m_node->child1());
+        LValue argument = lowJSValue(m_node->child2());
         setBoolean(
-            vmCall(Int32, m_out.operation(operationRegExpTest), m_callFrame, base, argument));
+            vmCall(Int32, m_out.operation(operationRegExpTestGeneric), m_callFrame, base, argument));
     }
 
     void compileNewRegexp()

Added: trunk/Source/_javascript_Core/tests/stress/regexp-exec-effect-after-exception.js (0 => 197492)


--- trunk/Source/_javascript_Core/tests/stress/regexp-exec-effect-after-exception.js	                        (rev 0)
+++ trunk/Source/_javascript_Core/tests/stress/regexp-exec-effect-after-exception.js	2016-03-03 05:58:59 UTC (rev 197492)
@@ -0,0 +1,26 @@
+function foo(s) {
+    return /.*/.exec(s);
+}
+
+noInline(foo);
+
+for (var i = 0; i < 10000; ++i)
+    foo("foo bar");
+
+RegExp.input = "blah";
+
+var didFinish = false;
+try {
+    foo({toString: function() {
+        throw "error";
+    }});
+    didFinish = true;
+} catch (e) {
+    if (e != "error")
+        throw "Error: bad exception at end: " + e;
+    if (RegExp.input != "blah")
+        throw "Error: bad value of input: " + RegExp.input;
+}
+
+if (didFinish)
+    throw "Error: did not throw exception.";
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to