Commit 10d23a1b authored by mark.lam@apple.com's avatar 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):



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@149089 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent c87c3b6f
......@@ -1068,6 +1068,18 @@ static double currentCPUTime()
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 @@ static bool extendTerminateCallback(JSContextRef ctx, void* context)
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 @@ int main(int argc, char* argv[])
}
#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 @@ int main(int argc, char* argv[])
}
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 @@ int main(int argc, char* argv[])
}
/* 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 @@ int main(int argc, char* argv[])
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 @@ int main(int argc, char* argv[])
}
/* 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 @@ int main(int argc, char* argv[])
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 @@ int main(int argc, char* argv[])
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;
}
......
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
......
......@@ -46,7 +46,8 @@ enum ExitKind {
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);
......
......@@ -1538,6 +1538,10 @@ bool AbstractState::executeEffects(unsigned indexInBlock, Node* node)
m_isValid = false;
break;
case CheckWatchdogTimer:
node->setCanExit(true);
break;
case Phantom:
case InlineStart:
case Nop:
......
......@@ -3285,9 +3285,13 @@ bool ByteCodeParser::parseBlock(unsigned limit)
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);
}
......
......@@ -859,6 +859,7 @@ private:
case GarbageValue:
case CountExecution:
case ForceOSRExit:
case CheckWatchdogTimer:
break;
#else
default:
......
......@@ -264,7 +264,11 @@ namespace JSC { namespace DFG {
/* 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).
......
......@@ -526,6 +526,7 @@ private:
case Phantom:
case PutGlobalVar:
case PutGlobalVarCheck:
case CheckWatchdogTimer:
break;
// These gets ignored because it doesn't do anything.
......
......@@ -4926,6 +4926,14 @@ void SpeculativeJIT::compile(Node* node)
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;
......
......@@ -4770,6 +4770,14 @@ void SpeculativeJIT::compile(Node* node)
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);
......
......@@ -79,11 +79,8 @@ void Watchdog::setTimeLimit(VM& vm, double limit,
// 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();
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment