Add watchdog timer polling for the DFG.
https://bugs.webkit.org/show_bug.cgi?id=115134.
Reviewed by Geoffrey Garen.
The strategy is to add a speculation check to the DFG generated code to
test if the watchdog timer has fired or not. If the watchdog timer has
fired, the generated code will do an OSR exit to the baseline JIT, and
let it handle servicing the watchdog timer.
If the watchdog is not enabled, this speculation check will not be
emitted.
* API/tests/testapi.c:
(currentCPUTime_callAsFunction):
(extendTerminateCallback):
(main):
- removed try/catch statements so that we can test the watchdog on the DFG.
- added JS bindings to a native currentCPUTime() function so that the timeout
tests can be more accurate.
- also shortened the time values so that the tests can complete sooner.
* bytecode/ExitKind.h:
* dfg/DFGAbstractState.cpp:
(JSC::DFG::AbstractState::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNodeType.h:
* dfg/DFGPredictionPropagationPhase.cpp:
(JSC::DFG::PredictionPropagationPhase::propagate):
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* runtime/Watchdog.cpp:
(JSC::Watchdog::setTimeLimit):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@149089 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/API/tests/testapi.c b/Source/JavaScriptCore/API/tests/testapi.c
index f286a81..baedc73 100644
--- a/Source/JavaScriptCore/API/tests/testapi.c
+++ b/Source/JavaScriptCore/API/tests/testapi.c
@@ -1068,6 +1068,18 @@
return time;
}
+static JSValueRef currentCPUTime_callAsFunction(JSContextRef ctx, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ UNUSED_PARAM(functionObject);
+ UNUSED_PARAM(thisObject);
+ UNUSED_PARAM(argumentCount);
+ UNUSED_PARAM(arguments);
+ UNUSED_PARAM(exception);
+
+ ASSERT(JSContextGetGlobalContext(ctx) == context);
+ return JSValueMakeNumber(ctx, currentCPUTime());
+}
+
bool shouldTerminateCallbackWasCalled = false;
static bool shouldTerminateCallback(JSContextRef ctx, void* context)
{
@@ -1093,7 +1105,7 @@
extendTerminateCallbackCalled++;
if (extendTerminateCallbackCalled == 1) {
JSContextGroupRef contextGroup = JSContextGetGroup(ctx);
- JSContextGroupSetExecutionTimeLimit(contextGroup, 2, extendTerminateCallback, 0);
+ JSContextGroupSetExecutionTimeLimit(contextGroup, .200f, extendTerminateCallback, 0);
return false;
}
return true;
@@ -1749,22 +1761,55 @@
}
#if PLATFORM(MAC) || PLATFORM(IOS)
+ JSStringRef currentCPUTimeStr = JSStringCreateWithUTF8CString("currentCPUTime");
+ JSObjectRef currentCPUTimeFunction = JSObjectMakeFunctionWithCallback(context, currentCPUTimeStr, currentCPUTime_callAsFunction);
+ JSObjectSetProperty(context, globalObject, currentCPUTimeStr, currentCPUTimeFunction, kJSPropertyAttributeNone, NULL);
+ JSStringRelease(currentCPUTimeStr);
+
/* Test script timeout: */
- JSContextGroupSetExecutionTimeLimit(contextGroup, 1, shouldTerminateCallback, 0);
+ JSContextGroupSetExecutionTimeLimit(contextGroup, .10f, shouldTerminateCallback, 0);
{
- const char* loopForeverScript = "startTime = Date.now(); try { while (true) { if (Date.now() - startTime > 5000) break; } } catch(e) { }";
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } ";
JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
double startTime;
double endTime;
exception = NULL;
+ shouldTerminateCallbackWasCalled = false;
startTime = currentCPUTime();
v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
endTime = currentCPUTime();
- if (((endTime - startTime) < 2) && shouldTerminateCallbackWasCalled)
+ if (((endTime - startTime) < .150f) && shouldTerminateCallbackWasCalled)
printf("PASS: script timed out as expected.\n");
else {
- if (!((endTime - startTime) < 2))
+ if (!((endTime - startTime) < .150f))
+ printf("FAIL: script did not timed out as expected.\n");
+ if (!shouldTerminateCallbackWasCalled)
+ printf("FAIL: script timeout callback was not called.\n");
+ failed = true;
+ }
+
+ if (!exception) {
+ printf("FAIL: TerminationExecutionException was not thrown.\n");
+ failed = true;
+ }
+ }
+
+ /* Test the script timeout's TerminationExecutionException should NOT be catchable: */
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, shouldTerminateCallback, 0);
+ {
+ const char* loopForeverScript = "var startTime = currentCPUTime(); try { while (true) { if (currentCPUTime() - startTime > .150) break; } } catch(e) { }";
+ JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
+ double startTime;
+ double endTime;
+ exception = NULL;
+ shouldTerminateCallbackWasCalled = false;
+ startTime = currentCPUTime();
+ v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
+ endTime = currentCPUTime();
+
+ if (((endTime - startTime) >= .150f) || !shouldTerminateCallbackWasCalled) {
+ if (!((endTime - startTime) < .150f))
printf("FAIL: script did not timed out as expected.\n");
if (!shouldTerminateCallbackWasCalled)
printf("FAIL: script timeout callback was not called.\n");
@@ -1772,7 +1817,7 @@
}
if (exception)
- printf("PASS: TerminationExecutionException was not catchable.\n");
+ printf("PASS: TerminationExecutionException was not catchable as expected.\n");
else {
printf("FAIL: TerminationExecutionException was caught.\n");
failed = true;
@@ -1780,9 +1825,9 @@
}
/* Test script timeout cancellation: */
- JSContextGroupSetExecutionTimeLimit(contextGroup, 1, cancelTerminateCallback, 0);
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.10f, cancelTerminateCallback, 0);
{
- const char* loopForeverScript = "startTime = Date.now(); try { while (true) { if (Date.now() - startTime > 5000) break; } } catch(e) { }";
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .150) break; } ";
JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
double startTime;
double endTime;
@@ -1791,10 +1836,10 @@
v = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
endTime = currentCPUTime();
- if (((endTime - startTime) >= 2) && cancelTerminateCallbackWasCalled && !exception)
+ if (((endTime - startTime) >= .150f) && cancelTerminateCallbackWasCalled && !exception)
printf("PASS: script timeout was cancelled as expected.\n");
else {
- if (((endTime - startTime) < 2) || exception)
+ if (((endTime - startTime) < .150) || exception)
printf("FAIL: script timeout was not cancelled.\n");
if (!cancelTerminateCallbackWasCalled)
printf("FAIL: script timeout callback was not called.\n");
@@ -1803,9 +1848,9 @@
}
/* Test script timeout extension: */
- JSContextGroupSetExecutionTimeLimit(contextGroup, 1, extendTerminateCallback, 0);
+ JSContextGroupSetExecutionTimeLimit(contextGroup, 0.100f, extendTerminateCallback, 0);
{
- const char* loopForeverScript = "startTime = Date.now(); try { while (true) { if (Date.now() - startTime > 5000) break; } } catch(e) { }";
+ const char* loopForeverScript = "var startTime = currentCPUTime(); while (true) { if (currentCPUTime() - startTime > .500) break; } ";
JSStringRef script = JSStringCreateWithUTF8CString(loopForeverScript);
double startTime;
double endTime;
@@ -1816,12 +1861,12 @@
endTime = currentCPUTime();
deltaTime = endTime - startTime;
- if ((deltaTime >= 3) && (deltaTime < 5) && (extendTerminateCallbackCalled == 2) && exception)
+ if ((deltaTime >= .300f) && (deltaTime < .500f) && (extendTerminateCallbackCalled == 2) && exception)
printf("PASS: script timeout was extended as expected.\n");
else {
- if (deltaTime < 2)
+ if (deltaTime < .200f)
printf("FAIL: script timeout was not extended as expected.\n");
- else if (deltaTime >= 5)
+ else if (deltaTime >= .500f)
printf("FAIL: script did not timeout.\n");
if (extendTerminateCallbackCalled < 1)
@@ -1830,7 +1875,7 @@
printf("FAIL: script timeout callback was not called after timeout extension.\n");
if (!exception)
- printf("FAIL: TerminationExecutionException was caught during timeout extension test.\n");
+ printf("FAIL: TerminationExecutionException was not thrown during timeout extension test.\n");
failed = true;
}
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index e2a5f72..1c2dca1 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,44 @@
+2013-04-24 Mark Lam <mark.lam@apple.com>
+
+ Add watchdog timer polling for the DFG.
+ https://bugs.webkit.org/show_bug.cgi?id=115134.
+
+ Reviewed by Geoffrey Garen.
+
+ The strategy is to add a speculation check to the DFG generated code to
+ test if the watchdog timer has fired or not. If the watchdog timer has
+ fired, the generated code will do an OSR exit to the baseline JIT, and
+ let it handle servicing the watchdog timer.
+
+ If the watchdog is not enabled, this speculation check will not be
+ emitted.
+
+ * API/tests/testapi.c:
+ (currentCPUTime_callAsFunction):
+ (extendTerminateCallback):
+ (main):
+ - removed try/catch statements so that we can test the watchdog on the DFG.
+ - added JS bindings to a native currentCPUTime() function so that the timeout
+ tests can be more accurate.
+ - also shortened the time values so that the tests can complete sooner.
+
+ * bytecode/ExitKind.h:
+ * dfg/DFGAbstractState.cpp:
+ (JSC::DFG::AbstractState::executeEffects):
+ * dfg/DFGByteCodeParser.cpp:
+ (JSC::DFG::ByteCodeParser::parseBlock):
+ * dfg/DFGFixupPhase.cpp:
+ (JSC::DFG::FixupPhase::fixupNode):
+ * dfg/DFGNodeType.h:
+ * dfg/DFGPredictionPropagationPhase.cpp:
+ (JSC::DFG::PredictionPropagationPhase::propagate):
+ * dfg/DFGSpeculativeJIT32_64.cpp:
+ (JSC::DFG::SpeculativeJIT::compile):
+ * dfg/DFGSpeculativeJIT64.cpp:
+ (JSC::DFG::SpeculativeJIT::compile):
+ * runtime/Watchdog.cpp:
+ (JSC::Watchdog::setTimeLimit):
+
2013-04-24 Filip Pizlo <fpizlo@apple.com>
Special thunks for math functions should work on ARMv7
diff --git a/Source/JavaScriptCore/bytecode/ExitKind.h b/Source/JavaScriptCore/bytecode/ExitKind.h
index 8b34f4e..af918ac 100644
--- a/Source/JavaScriptCore/bytecode/ExitKind.h
+++ b/Source/JavaScriptCore/bytecode/ExitKind.h
@@ -46,7 +46,8 @@
ArgumentsEscaped, // We exited because arguments escaped but we didn't expect them to.
NotStringObject, // We exited because we shouldn't have attempted to optimize string object access.
Uncountable, // We exited for none of the above reasons, and we should not count it. Most uses of this should be viewed as a FIXME.
- UncountableWatchpoint // We exited because of a watchpoint, which isn't counted because watchpoints do tracking themselves.
+ UncountableWatchpoint, // We exited because of a watchpoint, which isn't counted because watchpoints do tracking themselves.
+ WatchdogTimerFired // We exited because we need to service the watchdog timer.
};
const char* exitKindToString(ExitKind);
diff --git a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
index 1352ca2..3c116a0 100644
--- a/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
+++ b/Source/JavaScriptCore/dfg/DFGAbstractState.cpp
@@ -1538,6 +1538,10 @@
m_isValid = false;
break;
+ case CheckWatchdogTimer:
+ node->setCanExit(true);
+ break;
+
case Phantom:
case InlineStart:
case Nop:
diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
index 5cdf48d..233f0ab 100644
--- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
+++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
@@ -3285,9 +3285,13 @@
if (!m_inlineStackTop->m_caller)
m_currentBlock->isOSRTarget = true;
- // Emit a phantom node to ensure that there is a placeholder node for this bytecode
- // op.
- addToGraph(Phantom);
+ if (m_vm->watchdog.isEnabled())
+ addToGraph(CheckWatchdogTimer);
+ else {
+ // Emit a phantom node to ensure that there is a placeholder
+ // node for this bytecode op.
+ addToGraph(Phantom);
+ }
NEXT_OPCODE(op_loop_hint);
}
diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
index 9d33f9f..a0bfca8 100644
--- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
@@ -859,6 +859,7 @@
case GarbageValue:
case CountExecution:
case ForceOSRExit:
+ case CheckWatchdogTimer:
break;
#else
default:
diff --git a/Source/JavaScriptCore/dfg/DFGNodeType.h b/Source/JavaScriptCore/dfg/DFGNodeType.h
index 0301f64..6ef9542 100644
--- a/Source/JavaScriptCore/dfg/DFGNodeType.h
+++ b/Source/JavaScriptCore/dfg/DFGNodeType.h
@@ -264,7 +264,11 @@
/* This is a pseudo-terminal. It means that execution should fall out of DFG at */\
/* this point, but execution does continue in the basic block - just in a */\
/* different compiler. */\
- macro(ForceOSRExit, NodeMustGenerate)
+ macro(ForceOSRExit, NodeMustGenerate) \
+ \
+ /* Checks the watchdog timer. If the timer has fired, we OSR exit to the */ \
+ /* baseline JIT to redo the watchdog timer check, and service the timer. */ \
+ macro(CheckWatchdogTimer, NodeMustGenerate) \
// This enum generates a monotonically increasing id for all Node types,
// and is used by the subsequent enum to fill out the id (as accessed via the NodeIdMask).
diff --git a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
index 8c67da9..3377f26 100644
--- a/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
+++ b/Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
@@ -526,6 +526,7 @@
case Phantom:
case PutGlobalVar:
case PutGlobalVarCheck:
+ case CheckWatchdogTimer:
break;
// These gets ignored because it doesn't do anything.
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
index 358441d..172afee 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
@@ -4926,6 +4926,14 @@
break;
}
+ case CheckWatchdogTimer:
+ speculationCheck(
+ WatchdogTimerFired, JSValueRegs(), 0,
+ m_jit.branchTest8(
+ JITCompiler::NonZero,
+ JITCompiler::AbsoluteAddress(m_jit.vm()->watchdog.timerDidFireAddress())));
+ break;
+
case CountExecution:
m_jit.add64(TrustedImm32(1), MacroAssembler::AbsoluteAddress(node->executionCounter()->address()));
break;
diff --git a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
index e04596c..e7a7791 100644
--- a/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
+++ b/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
@@ -4770,6 +4770,14 @@
break;
}
+ case CheckWatchdogTimer:
+ speculationCheck(
+ WatchdogTimerFired, JSValueRegs(), 0,
+ m_jit.branchTest8(
+ JITCompiler::NonZero,
+ JITCompiler::AbsoluteAddress(m_jit.vm()->watchdog.timerDidFireAddress())));
+ break;
+
case Phantom:
DFG_NODE_DO_TO_CHILDREN(m_jit.graph(), node, speculate);
noResult(node);
diff --git a/Source/JavaScriptCore/runtime/Watchdog.cpp b/Source/JavaScriptCore/runtime/Watchdog.cpp
index 4ee41d5..573260b 100644
--- a/Source/JavaScriptCore/runtime/Watchdog.cpp
+++ b/Source/JavaScriptCore/runtime/Watchdog.cpp
@@ -79,11 +79,8 @@
// timeout value, then any existing JITted code will have the appropriate
// polling checks. Hence, there is no need to re-do this flushing.
if (!wasEnabled) {
- // For now, we only support this feature when the DFG JIT is disabled.
- Options::useDFGJIT() = false;
-
- // And if we've previously compiled any functions, we need to deopt
- // them because they don't habe the needed polling checks yet.
+ // And if we've previously compiled any functions, we need to revert
+ // them because they don't have the needed polling checks yet.
vm.releaseExecutableMemory();
}