Commit c8f3a755 authored by oliver@apple.com's avatar oliver@apple.com

Bug 18626: SQUIRRELFISH: support the "slow script" dialog...

Bug 18626: SQUIRRELFISH: support the "slow script" dialog <https://bugs.webkit.org/show_bug.cgi?id=18626>
<rdar://problem/5973931> Slow script dialog needs to be reimplemented for squirrelfish

Reviewed by Sam

Adds support for the slow script dialog in squirrelfish.  This requires the addition
of three new op codes, op_loop, op_loop_if_true, and op_loop_if_less which have the
same behaviour as their simple jump equivalents but have an additional time out check.

Additional assertions were added to other jump instructions to prevent accidentally
creating loops with jump types that do not support time out checks.

Sunspider does not report a regression, however this appears very sensitive to code
layout and hardware, so i would expect up to a 1% regression on other systems.

Part of this required moving the old timeout logic from JSGlobalObject and into Machine
which is the cause of a number of the larger diff blocks.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@34842 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 2d7400ee
2008-06-27 Oliver Hunt <oliver@apple.com>
Reviewed by Sam.
Bug 18626: SQUIRRELFISH: support the "slow script" dialog <https://bugs.webkit.org/show_bug.cgi?id=18626>
<rdar://problem/5973931> Slow script dialog needs to be reimplemented for squirrelfish
Adds support for the slow script dialog in squirrelfish. This requires the addition
of three new op codes, op_loop, op_loop_if_true, and op_loop_if_less which have the
same behaviour as their simple jump equivalents but have an additional time out check.
Additional assertions were added to other jump instructions to prevent accidentally
creating loops with jump types that do not support time out checks.
Sunspider does not report a regression, however this appears very sensitive to code
layout and hardware, so i would expect up to a 1% regression on other systems.
Part of this required moving the old timeout logic from JSGlobalObject and into Machine
which is the cause of a number of the larger diff blocks.
* JavaScriptCore.exp:
* VM/CodeBlock.cpp:
(KJS::CodeBlock::dump):
* VM/CodeGenerator.cpp:
(KJS::CodeGenerator::emitJumpIfTrue):
(KJS::CodeGenerator::emitJumpScopes):
* VM/ExceptionHelpers.cpp:
(KJS::InterruptedExecutionError::isWatchdogException):
(KJS::createInterruptedExecutionException):
* VM/ExceptionHelpers.h:
* VM/LabelID.h:
* VM/Machine.cpp:
(KJS::Machine::Machine):
(KJS::Machine::throwException):
(KJS::Machine::resetTimeoutCheck):
(KJS::getCurrentTime):
(KJS::Machine::checkTimeout):
(KJS::Machine::privateExecute):
* VM/Machine.h:
(KJS::Machine::setTimeoutTime):
(KJS::Machine::startTimeoutCheck):
(KJS::Machine::stopTimeoutCheck):
(KJS::Machine::initTimeout):
* VM/Opcode.cpp:
(KJS::):
* VM/Opcode.h:
* kjs/JSGlobalObject.cpp:
(KJS::JSGlobalObject::init):
(KJS::JSGlobalObject::setTimeoutTime):
(KJS::JSGlobalObject::startTimeoutCheck):
* kjs/JSGlobalObject.h:
* kjs/JSObject.h:
* kjs/interpreter.cpp:
(KJS::Interpreter::evaluate):
2008-06-27 Jan Michael Alonzo <jmalonzo@webkit.org>
Gtk and Qt build fix: Remove RegisterFileStack from the build
......
......@@ -115,6 +115,7 @@ __ZN3KJS13jsOwnedStringEPNS_9ExecStateERKNS_7UStringE
__ZN3KJS14JSGlobalObject10globalExecEv
__ZN3KJS14JSGlobalObject12defineGetterEPNS_9ExecStateERKNS_10IdentifierEPNS_8JSObjectE
__ZN3KJS14JSGlobalObject12defineSetterEPNS_9ExecStateERKNS_10IdentifierEPNS_8JSObjectE
__ZN3KJS14JSGlobalObject14setTimeoutTimeEj
__ZN3KJS14JSGlobalObject16stopTimeoutCheckEv
__ZN3KJS14JSGlobalObject17putWithAttributesEPNS_9ExecStateERKNS_10IdentifierEPNS_7JSValueEj
__ZN3KJS14JSGlobalObject17startTimeoutCheckEv
......
......@@ -462,10 +462,19 @@ void CodeBlock::dump(ExecState* exec, const Vector<Instruction>::const_iterator&
printf("[%4d] jmp\t\t %d(->%d)\n", location, offset, jumpTarget(begin, it, offset));
break;
}
case op_loop: {
int offset = (++it)->u.operand;
printf("[%4d] loop\t\t %d(->%d)\n", location, offset, jumpTarget(begin, it, offset));
break;
}
case op_jtrue: {
printConditionalJump(begin, it, location, "jtrue");
break;
}
case op_loop_if_true: {
printConditionalJump(begin, it, location, "loop_if_true");
break;
}
case op_jfalse: {
printConditionalJump(begin, it, location, "jfalse");
break;
......@@ -477,6 +486,13 @@ void CodeBlock::dump(ExecState* exec, const Vector<Instruction>::const_iterator&
printf("[%4d] jless\t\t %s, %s, %d(->%d)\n", location, registerName(r0).c_str(), registerName(r1).c_str(), offset, jumpTarget(begin, it, offset));
break;
}
case op_loop_if_less: {
int r0 = (++it)->u.operand;
int r1 = (++it)->u.operand;
int offset = (++it)->u.operand;
printf("[%4d] loop_if_less %s, %s, %d(->%d)\n", location, registerName(r0).c_str(), registerName(r1).c_str(), offset, jumpTarget(begin, it, offset));
break;
}
case op_new_func: {
int r0 = (++it)->u.operand;
int f0 = (++it)->u.operand;
......
......@@ -437,7 +437,7 @@ void CodeGenerator::rewindBinaryOp()
PassRefPtr<LabelID> CodeGenerator::emitJump(LabelID* target)
{
emitOpcode(op_jmp);
emitOpcode(target->isForwardLabel() ? op_jmp : op_loop);
instructions().append(target->offsetFrom(instructions().size()));
return target;
}
......@@ -453,7 +453,7 @@ PassRefPtr<LabelID> CodeGenerator::emitJumpIfTrue(RegisterID* cond, LabelID* tar
if (cond->index() == dstIndex && !cond->refCount()) {
rewindBinaryOp();
emitOpcode(op_jless);
emitOpcode(target->isForwardLabel() ? op_jless : op_loop_if_less);
instructions().append(src1Index);
instructions().append(src2Index);
instructions().append(target->offsetFrom(instructions().size()));
......@@ -461,7 +461,7 @@ PassRefPtr<LabelID> CodeGenerator::emitJumpIfTrue(RegisterID* cond, LabelID* tar
}
}
emitOpcode(op_jtrue);
emitOpcode(target->isForwardLabel() ? op_jtrue : op_loop_if_true);
instructions().append(cond->index());
instructions().append(target->offsetFrom(instructions().size()));
return target;
......@@ -469,6 +469,7 @@ PassRefPtr<LabelID> CodeGenerator::emitJumpIfTrue(RegisterID* cond, LabelID* tar
PassRefPtr<LabelID> CodeGenerator::emitJumpIfFalse(RegisterID* cond, LabelID* target)
{
ASSERT(target->isForwardLabel());
emitOpcode(op_jfalse);
instructions().append(cond->index());
instructions().append(target->offsetFrom(instructions().size()));
......@@ -1043,6 +1044,7 @@ PassRefPtr<LabelID> CodeGenerator::emitComplexJumpScopes(LabelID* target, Contro
PassRefPtr<LabelID> CodeGenerator::emitJumpScopes(LabelID* target, int targetScopeDepth)
{
ASSERT(scopeDepth() - targetScopeDepth >= 0);
ASSERT(target->isForwardLabel());
size_t scopeDelta = scopeDepth() - targetScopeDepth;
ASSERT(scopeDelta <= m_scopeContextStack.size());
......
......@@ -45,6 +45,16 @@ static void substitute(UString& string, const UString& substring)
string = newString;
}
class InterruptedExecutionError : public JSObject {
public:
virtual bool isWatchdogException() const { return true; }
};
JSValue* createInterruptedExecutionException(ExecState* exec)
{
return new (exec) InterruptedExecutionError;
}
JSValue* createError(ExecState* exec, ErrorType e, const char* msg)
{
return Error::create(exec, e, msg, -1, -1, 0);
......
......@@ -35,6 +35,7 @@ namespace KJS {
class Node;
JSValue* createInterruptedExecutionException(ExecState* exec);
JSValue* createStackOverflowError(ExecState*);
JSValue* createUndefinedVariableError(ExecState*, const Identifier&);
JSValue* createInvalidParamError(ExecState*, const char* op, JSValue*);
......
......@@ -98,6 +98,8 @@ namespace KJS {
return m_refCount;
}
bool isForwardLabel() const { return m_location == invalidLocation; }
private:
typedef Vector<int, 8> JumpVector;
......
......@@ -49,10 +49,28 @@
#include "operations.h"
#include "RegExpObject.h"
#if HAVE(SYS_TIME_H)
#include <sys/time.h>
#endif
#if PLATFORM(WIN_OS)
#include <windows.h>
#endif
#if PLATFORM(QT)
#include <QDateTime>
#endif
using namespace std;
namespace KJS {
// Default number of ticks before a timeout check should be done.
static const int initialTickCountThreshold = 255;
// Preferred number of milliseconds between each timeout check
static const int preferredScriptCheckTimeInterval = 1000;
#if HAVE(COMPUTED_GOTO)
static void* op_throw_end_indirect;
static void* op_call_indirect;
......@@ -452,6 +470,11 @@ NEVER_INLINE JSValue* callEval(ExecState* exec, JSObject* thisObj, ScopeChainNod
Machine::Machine()
: m_reentryDepth(0)
, m_timeoutTime(0)
, m_timeAtLastCheckTimeout(0)
, m_timeExecuting(0)
, m_timeoutCheckCount(0)
, m_ticksUntilNextTimeoutCheck(initialTickCountThreshold)
{
privateExecute(InitializeAndReturn);
}
......@@ -591,6 +614,13 @@ NEVER_INLINE Instruction* Machine::throwException(ExecState* exec, JSValue* exce
exception->put(exec, Identifier(exec, "line"), jsNumber(exec, codeBlock->lineNumberForVPC(vPC)));
exception->put(exec, Identifier(exec, "sourceURL"), jsOwnedString(exec, codeBlock->ownerNode->sourceURL()));
}
if (exception->isWatchdogException()) {
while (unwindCallFrame(exec, exceptionValue, registerBase, vPC, codeBlock, k, scopeChain, r)) {
// Don't need handler checks or anything, we just want to unroll all the JS callframes possible.
}
return 0;
}
}
if (Debugger* debugger = exec->dynamicGlobalObject()->debugger()) {
......@@ -847,6 +877,72 @@ NEVER_INLINE void Machine::debug(ExecState* exec, const Instruction* vPC, const
}
}
void Machine::resetTimeoutCheck()
{
m_ticksUntilNextTimeoutCheck = initialTickCountThreshold;
m_timeAtLastCheckTimeout = 0;
m_timeExecuting = 0;
}
// Returns the current time in milliseconds
// It doesn't matter what "current time" is here, just as long as
// it's possible to measure the time difference correctly.
// In an ideal world this would be in DateMath or some such, but unfortunately
// that's a regression.
static inline unsigned getCurrentTime()
{
#if HAVE(SYS_TIME_H)
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#elif PLATFORM(QT)
QDateTime t = QDateTime::currentDateTime();
return t.toTime_t() * 1000 + t.time().msec();
#elif PLATFORM(WIN_OS)
return timeGetTime();
#else
#error Platform does not have getCurrentTime function
#endif
}
// We have to return a JSValue here, gcc seems to produce worse code if
// we attempt to return a bool
ALWAYS_INLINE JSValue* Machine::checkTimeout(JSGlobalObject* globalObject)
{
unsigned currentTime = getCurrentTime();
if (!m_timeAtLastCheckTimeout) {
// Suspicious amount of looping in a script -- start timing it
m_timeAtLastCheckTimeout = currentTime;
return 0;
}
unsigned timeDiff = currentTime - m_timeAtLastCheckTimeout;
if (timeDiff == 0)
timeDiff = 1;
m_timeExecuting += timeDiff;
m_timeAtLastCheckTimeout = currentTime;
// Adjust the tick threshold so we get the next checkTimeout call in the interval specified in
// preferredScriptCheckTimeInterval
m_ticksUntilNextTimeoutCheck = static_cast<unsigned>((static_cast<float>(preferredScriptCheckTimeInterval) / timeDiff) * m_ticksUntilNextTimeoutCheck);
// If the new threshold is 0 reset it to the default threshold. This can happen if the timeDiff is higher than the
// preferred script check time interval.
if (m_ticksUntilNextTimeoutCheck == 0)
m_ticksUntilNextTimeoutCheck = initialTickCountThreshold;
if (m_timeoutTime && m_timeExecuting > m_timeoutTime) {
if (globalObject->shouldInterruptScript())
return jsNull(); // Appeasing GCC, all we need is a non-null js value.
resetTimeoutCheck();
}
return 0;
}
JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFile* registerFile, Register* r, ScopeChainNode* scopeChain, CodeBlock* codeBlock, JSValue** exception)
{
// One-time initialization of our address tables. We have to put this code
......@@ -874,7 +970,8 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
Instruction* vPC = codeBlock->instructions.begin();
JSValue** k = codeBlock->jsValues.data();
Profiler** enabledProfilerReference = Profiler::enabledProfilerReference();
unsigned tickCount = m_ticksUntilNextTimeoutCheck + 1;
#define VM_CHECK_EXCEPTION() \
do { \
if (UNLIKELY(exec->hadException())) { \
......@@ -887,6 +984,13 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
OpcodeStats::resetLastInstruction();
#endif
#define CHECK_FOR_TIMEOUT() \
if (!--tickCount) { \
if ((exceptionValue = checkTimeout(exec->dynamicGlobalObject()))) \
goto vm_throw; \
tickCount = m_ticksUntilNextTimeoutCheck; \
}
#if HAVE(COMPUTED_GOTO)
#define NEXT_OPCODE goto *vPC->u.opcode
#if DUMP_OPCODE_STATS
......@@ -1843,6 +1947,23 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
++vPC;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_loop) {
/* loop target(offset)
Jumps unconditionally to offset target from the current
instruction.
Additionally this loop instruction may terminate JS execution is
the JS timeout is reached.
*/
#if DUMP_OPCODE_STATS
OpcodeStats::resetLastInstruction();
#endif
int target = (++vPC)->u.operand;
CHECK_FOR_TIMEOUT();
vPC += target;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_jmp) {
/* jmp target(offset)
......@@ -1857,6 +1978,26 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
vPC += target;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_loop_if_true) {
/* loop_if_true cond(r) target(offset)
Jumps to offset target from the current instruction, if and
only if register cond converts to boolean as true.
Additionally this loop instruction may terminate JS execution is
the JS timeout is reached.
*/
int cond = (++vPC)->u.operand;
int target = (++vPC)->u.operand;
if (r[cond].u.jsValue->toBoolean(exec)) {
vPC += target;
CHECK_FOR_TIMEOUT();
NEXT_OPCODE;
}
++vPC;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_jtrue) {
/* jtrue cond(r) target(offset)
......@@ -1889,6 +2030,33 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
++vPC;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_loop_if_less) {
/* loop_if_less src1(r) src2(r) target(offset)
Checks whether register src1 is less than register src2, as
with the ECMAScript '<' operator, and then jumps to offset
target from the current instruction, if and only if the
result of the comparison is true.
Additionally this loop instruction may terminate JS execution is
the JS timeout is reached.
*/
JSValue* src1 = r[(++vPC)->u.operand].u.jsValue;
JSValue* src2 = r[(++vPC)->u.operand].u.jsValue;
int target = (++vPC)->u.operand;
bool result = jsLess(exec, src1, src2);
VM_CHECK_EXCEPTION();
if (result) {
vPC += target;
CHECK_FOR_TIMEOUT();
NEXT_OPCODE;
}
++vPC;
NEXT_OPCODE;
}
BEGIN_OPCODE(op_jless) {
/* jless src1(r) src2(r) target(offset)
......@@ -2284,6 +2452,7 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
JSPropertyNameIterator* it = r[iter].u.jsPropertyNameIterator;
if (JSValue* temp = it->next(exec)) {
CHECK_FOR_TIMEOUT();
r[dst].u.jsValue = temp;
vPC += target;
NEXT_OPCODE;
......@@ -2475,6 +2644,11 @@ JSValue* Machine::privateExecute(ExecutionFlag flag, ExecState* exec, RegisterFi
}
vm_throw: {
exec->clearException();
if (!tickCount) {
// The exceptionValue is a lie! (GCC produces bad code for reasons I
// cannot fathom if we don't assign to the exceptionValue before branching)
exceptionValue = createInterruptedExecutionException(exec);
}
handlerVPC = throwException(exec, exceptionValue, &registerBase, vPC, codeBlock, k, scopeChain, r);
if (!handlerVPC) {
*exception = exceptionValue;
......
......@@ -42,6 +42,7 @@ namespace KJS {
class FunctionBodyNode;
class Instruction;
class JSFunction;
class JSGlobalObject;
class ProgramNode;
class Register;
class RegisterFile;
......@@ -96,7 +97,27 @@ namespace KJS {
JSValue* retrieveCaller(ExecState*, JSFunction*) const;
void getFunctionAndArguments(Register** registerBase, Register* callFrame, JSFunction*&, Register*& argv, int& argc);
void setTimeoutTime(unsigned timeoutTime) { m_timeoutTime = timeoutTime; }
void startTimeoutCheck()
{
if (!m_timeoutCheckCount)
resetTimeoutCheck();
++m_timeoutCheckCount;
}
void stopTimeoutCheck()
{
--m_timeoutCheckCount;
}
inline void initTimeout()
{
resetTimeoutCheck();
m_timeoutTime = 0;
m_timeoutCheckCount = 0;
}
void mark(Heap* heap) { m_registerFile.mark(heap); }
private:
......@@ -118,7 +139,16 @@ namespace KJS {
void dumpCallFrame(const CodeBlock*, ScopeChainNode*, RegisterFile*, const Register*);
void dumpRegisters(const CodeBlock*, RegisterFile*, const Register*);
JSValue* checkTimeout(JSGlobalObject*);
void resetTimeoutCheck();
int m_reentryDepth;
unsigned m_timeoutTime;
unsigned m_timeAtLastCheckTimeout;
unsigned m_timeExecuting;
unsigned m_timeoutCheckCount;
unsigned m_ticksUntilNextTimeoutCheck;
RegisterFile m_registerFile;
#if HAVE(COMPUTED_GOTO)
......
......@@ -105,6 +105,9 @@ static const char* opcodeNames[] = {
"jfalse ",
"jless ",
"jmp_scopes ",
"loop ",
"loop_if_true",
"loop_if_less",
"new_func ",
"new_func_exp",
......
......@@ -97,6 +97,9 @@ namespace KJS {
macro(op_jfalse) \
macro(op_jless) \
macro(op_jmp_scopes) \
macro(op_loop) \
macro(op_loop_if_true) \
macro(op_loop_if_less) \
\
macro(op_new_func) \
macro(op_new_func_exp) \
......
......@@ -45,18 +45,6 @@
#include "ScopeChainMark.h"
#include "string_object.h"
#if HAVE(SYS_TIME_H)
#include <sys/time.h>
#endif
#if PLATFORM(WIN_OS)
#include <windows.h>
#endif
#if PLATFORM(QT)
#include <QDateTime>
#endif
namespace KJS {
// Default number of ticks before a timeout check should be done.
......@@ -70,25 +58,6 @@ static inline void markIfNeeded(JSValue* v)
if (v && !v->marked())
v->mark();
}
// Returns the current time in milliseconds
// It doesn't matter what "current time" is here, just as long as
// it's possible to measure the time difference correctly.
static inline unsigned getCurrentTime()
{
#if HAVE(SYS_TIME_H)
struct timeval tv;
gettimeofday(&tv, 0);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#elif PLATFORM(QT)
QDateTime t = QDateTime::currentDateTime();
return t.toTime_t() * 1000 + t.time().msec();
#elif PLATFORM(WIN_OS)
return timeGetTime();
#else
#error Platform does not have getCurrentTime function
#endif
}
JSGlobalObject::~JSGlobalObject()
{
......@@ -131,13 +100,10 @@ void JSGlobalObject::init(JSObject* thisValue)
} else
headObject = d()->next = d()->prev = this;
resetTimeoutCheck();
d()->timeoutTime = 0;
d()->timeoutCheckCount = 0;
d()->recursion = 0;
d()->debugger = 0;
globalData()->machine->initTimeout();
d()->globalExec.set(new ExecState(this, thisValue, d()->globalScopeChain.node()));
d()->pageGroupIdentifier = 0;
......@@ -346,64 +312,19 @@ void JSGlobalObject::reset(JSValue* prototype)
lastInPrototypeChain(this)->setPrototype(d()->objectPrototype);
}
void JSGlobalObject::startTimeoutCheck()
void JSGlobalObject::setTimeoutTime(unsigned timeoutTime)
{
if (!d()->timeoutCheckCount)
resetTimeoutCheck();
++d()->timeoutCheckCount;
globalData()->machine->setTimeoutTime(timeoutTime);
}
void JSGlobalObject::stopTimeoutCheck()
void JSGlobalObject::startTimeoutCheck()
{
--d()->timeoutCheckCount;
globalData()->machine->startTimeoutCheck();
}
void JSGlobalObject::resetTimeoutCheck()
void JSGlobalObject::stopTimeoutCheck()
{
d()->tickCount = 0;
d()->ticksUntilNextTimeoutCheck = initialTickCountThreshold;
d()->timeAtLastCheckTimeout = 0;
d()->timeExecuting = 0;
}
bool JSGlobalObject::checkTimeout()
{
d()->tickCount = 0;
unsigned currentTime = getCurrentTime();
if (!d()->timeAtLastCheckTimeout) {
// Suspicious amount of looping in a script -- start timing it
d()->timeAtLastCheckTimeout = currentTime;
return false;
}
unsigned timeDiff = currentTime - d()->timeAtLastCheckTimeout;
if (timeDiff == 0)
timeDiff = 1;
d()->timeExecuting += timeDiff;
d()->timeAtLastCheckTimeout = currentTime;
// Adjust the tick threshold so we get the next checkTimeout call in the interval specified in
// preferredScriptCheckTimeInterval
d()->ticksUntilNextTimeoutCheck = (unsigned)((float)preferredScriptCheckTimeInterval / timeDiff) * d()->ticksUntilNextTimeoutCheck;
// If the new threshold is 0 reset it to the default threshold. This can happen if the timeDiff is higher than the
// preferred script check time interval.
if (d()->ticksUntilNextTimeoutCheck == 0)
d()->ticksUntilNextTimeoutCheck = initialTickCountThreshold;
if (d()->timeoutTime && d()->timeExecuting > d()->timeoutTime) {
if (shouldInterruptScript())
return true;
resetTimeoutCheck();
}
return false;
globalData()->machine->stopTimeoutCheck();
}
void JSGlobalObject::mark()
......
......@@ -89,13 +89,6 @@ namespace KJS {
int recursion;
unsigned timeoutTime;
unsigned timeAtLastCheckTimeout;
unsigned timeExecuting;