#include "config.h"
#include "JSLock.h"
-#include "Collector.h"
+#include "Heap.h"
#include "CallFrame.h"
+#include "JSGlobalObject.h"
+#include "JSObject.h"
+#include "ScopeChain.h"
-#if ENABLE(JSC_MULTIPLE_THREADS)
+#if USE(PTHREADS)
#include <pthread.h>
#endif
namespace JSC {
-#if ENABLE(JSC_MULTIPLE_THREADS)
+// JSLock is only needed to support an obsolete execution model where JavaScriptCore
+// automatically protected against concurrent access from multiple threads.
+// So it's safe to disable it on non-mac platforms where we don't have native pthreads.
+#if (OS(DARWIN) || USE(PTHREADS))
-// Acquire this mutex before accessing lock-related data.
-static pthread_mutex_t JSMutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t sharedInstanceLock = PTHREAD_MUTEX_INITIALIZER;
-// Thread-specific key that tells whether a thread holds the JSMutex, and how many times it was taken recursively.
-pthread_key_t JSLockCount;
+GlobalJSLock::GlobalJSLock()
+{
+ pthread_mutex_lock(&sharedInstanceLock);
+}
-static void createJSLockCount()
+GlobalJSLock::~GlobalJSLock()
{
- pthread_key_create(&JSLockCount, 0);
+ pthread_mutex_unlock(&sharedInstanceLock);
}
-pthread_once_t createJSLockCountOnce = PTHREAD_ONCE_INIT;
+JSLockHolder::JSLockHolder(ExecState* exec)
+ : m_globalData(&exec->globalData())
+{
+ m_globalData->apiLock().lock();
+}
-// Lock nesting count.
-intptr_t JSLock::lockCount()
+JSLockHolder::JSLockHolder(JSGlobalData* globalData)
+ : m_globalData(globalData)
{
- pthread_once(&createJSLockCountOnce, createJSLockCount);
+ m_globalData->apiLock().lock();
+}
- return reinterpret_cast<intptr_t>(pthread_getspecific(JSLockCount));
+JSLockHolder::JSLockHolder(JSGlobalData& globalData)
+ : m_globalData(&globalData)
+{
+ m_globalData->apiLock().lock();
}
-static void setLockCount(intptr_t count)
+JSLockHolder::~JSLockHolder()
{
- ASSERT(count >= 0);
- pthread_setspecific(JSLockCount, reinterpret_cast<void*>(count));
+ m_globalData->apiLock().unlock();
}
-JSLock::JSLock(ExecState* exec)
- : m_lockingForReal(exec->globalData().isSharedInstance)
+JSLock::JSLock()
+ : m_lockCount(0)
{
- lock(m_lockingForReal);
+ m_spinLock.Init();
}
-void JSLock::lock(bool lockForReal)
+JSLock::~JSLock()
{
-#ifdef NDEBUG
- // Locking "not for real" is a debug-only feature.
- if (!lockForReal)
- return;
-#endif
+}
+
+void JSLock::lock()
+{
+ ThreadIdentifier currentThread = WTF::currentThread();
+ {
+ SpinLockHolder holder(&m_spinLock);
+ if (m_ownerThread == currentThread && m_lockCount) {
+ m_lockCount++;
+ return;
+ }
+ }
- pthread_once(&createJSLockCountOnce, createJSLockCount);
+ m_lock.lock();
- intptr_t currentLockCount = lockCount();
- if (!currentLockCount && lockForReal) {
- int result;
- result = pthread_mutex_lock(&JSMutex);
- ASSERT(!result);
+ {
+ SpinLockHolder holder(&m_spinLock);
+ m_ownerThread = currentThread;
+ ASSERT(!m_lockCount);
+ m_lockCount = 1;
}
- setLockCount(currentLockCount + 1);
}
-void JSLock::unlock(bool lockForReal)
+void JSLock::unlock()
{
- ASSERT(lockCount());
+ ASSERT(currentThreadIsHoldingLock());
-#ifdef NDEBUG
- // Locking "not for real" is a debug-only feature.
- if (!lockForReal)
- return;
-#endif
+ SpinLockHolder holder(&m_spinLock);
+ m_lockCount--;
- intptr_t newLockCount = lockCount() - 1;
- setLockCount(newLockCount);
- if (!newLockCount && lockForReal) {
- int result;
- result = pthread_mutex_unlock(&JSMutex);
- ASSERT(!result);
- }
+ if (!m_lockCount)
+ m_lock.unlock();
}
void JSLock::lock(ExecState* exec)
{
- lock(exec->globalData().isSharedInstance);
+ exec->globalData().apiLock().lock();
}
void JSLock::unlock(ExecState* exec)
{
- unlock(exec->globalData().isSharedInstance);
+ exec->globalData().apiLock().unlock();
}
bool JSLock::currentThreadIsHoldingLock()
{
- pthread_once(&createJSLockCountOnce, createJSLockCount);
- return !!pthread_getspecific(JSLockCount);
+ return m_lockCount && m_ownerThread == WTF::currentThread();
}
// This is fairly nasty. We allow multiple threads to run on the same
// this to happen, and were its stack to grow further, then it may potentially
// write over the second thread's call frames.
//
-// In avoid JS stack corruption we enforce a policy of only ever allowing two
+// To avoid JS stack corruption we enforce a policy of only ever allowing two
// threads to use a JS context concurrently, and only allowing the second of
// these threads to execute until it has completed and fully returned from its
// outermost call into JSC. We enforce this policy using 'lockDropDepth'. The
// same thread again, enter JSC (through evaluate script or call function), and exit
// again through a callback, then the locks will not be dropped when DropAllLocks
// is called (since lockDropDepth is non-zero). Since this thread is still holding
-// the locks, only it will re able to re-enter JSC (either be returning from the
+// the locks, only it will be able to re-enter JSC (either be returning from the
// callback, or by re-entering through another call to evaulate script or call
// function).
//
// order in which they were made - though implementing the less restrictive policy
// would likely increase complexity and overhead.
//
-static unsigned lockDropDepth = 0;
-JSLock::DropAllLocks::DropAllLocks(ExecState* exec)
- : m_lockingForReal(exec->globalData().isSharedInstance)
+// This function returns the number of locks that were dropped.
+unsigned JSLock::dropAllLocks()
{
- pthread_once(&createJSLockCountOnce, createJSLockCount);
-
- if (lockDropDepth++) {
- m_lockCount = 0;
- return;
+ unsigned lockCount;
+ {
+ // Check if this thread is currently holding the lock.
+ // FIXME: Maybe we want to require this, guard with an ASSERT?
+ SpinLockHolder holder(&m_spinLock);
+ lockCount = m_lockCount;
+ if (!lockCount || m_ownerThread != WTF::currentThread())
+ return 0;
}
- m_lockCount = JSLock::lockCount();
- for (intptr_t i = 0; i < m_lockCount; i++)
- JSLock::unlock(m_lockingForReal);
+ // Don't drop the locks if they've already been dropped once.
+ // (If the prior drop came from another thread, and it resumed first,
+ // it could trash our register file).
+ if (m_lockDropDepth)
+ return 0;
+
+ // m_lockDropDepth is only incremented if any locks were dropped.
+ m_lockDropDepth++;
+ m_lockCount = 0;
+ m_lock.unlock();
+ return lockCount;
}
-JSLock::DropAllLocks::DropAllLocks(bool lockingForReal)
- : m_lockingForReal(lockingForReal)
+unsigned JSLock::dropAllLocksUnconditionally()
{
- pthread_once(&createJSLockCountOnce, createJSLockCount);
+ unsigned lockCount;
+ {
+ // Check if this thread is currently holding the lock.
+ // FIXME: Maybe we want to require this, guard with an ASSERT?
+ SpinLockHolder holder(&m_spinLock);
+ lockCount = m_lockCount;
+ if (!lockCount || m_ownerThread != WTF::currentThread())
+ return 0;
+ }
+
+ // m_lockDropDepth is only incremented if any locks were dropped.
+ m_lockDropDepth++;
+ m_lockCount = 0;
+ m_lock.unlock();
+ return lockCount;
+}
- if (lockDropDepth++) {
- m_lockCount = 0;
+void JSLock::grabAllLocks(unsigned lockCount)
+{
+ // If no locks were dropped, nothing to do!
+ if (!lockCount)
return;
+
+ ThreadIdentifier currentThread = WTF::currentThread();
+ {
+ // Check if this thread is currently holding the lock.
+ // FIXME: Maybe we want to prohibit this, guard against with an ASSERT?
+ SpinLockHolder holder(&m_spinLock);
+ if (m_ownerThread == currentThread && m_lockCount) {
+ m_lockCount += lockCount;
+ m_lockDropDepth--;
+ return;
+ }
}
- // It is necessary to drop even "unreal" locks, because having a non-zero lock count
- // will prevent a real lock from being taken.
+ m_lock.lock();
- m_lockCount = JSLock::lockCount();
- for (intptr_t i = 0; i < m_lockCount; i++)
- JSLock::unlock(m_lockingForReal);
+ {
+ SpinLockHolder holder(&m_spinLock);
+ m_ownerThread = currentThread;
+ ASSERT(!m_lockCount);
+ m_lockCount = lockCount;
+ m_lockDropDepth--;
+ }
+}
+
+JSLock::DropAllLocks::DropAllLocks(ExecState* exec, AlwaysDropLocksTag alwaysDropLocks)
+ : m_lockCount(0)
+ , m_globalData(&exec->globalData())
+{
+ if (alwaysDropLocks)
+ m_lockCount = m_globalData->apiLock().dropAllLocksUnconditionally();
+ else
+ m_lockCount = m_globalData->apiLock().dropAllLocks();
+}
+
+JSLock::DropAllLocks::DropAllLocks(JSGlobalData* globalData, AlwaysDropLocksTag alwaysDropLocks)
+ : m_lockCount(0)
+ , m_globalData(globalData)
+{
+ if (alwaysDropLocks)
+ m_lockCount = m_globalData->apiLock().dropAllLocksUnconditionally();
+ else
+ m_lockCount = m_globalData->apiLock().dropAllLocks();
}
JSLock::DropAllLocks::~DropAllLocks()
{
- for (intptr_t i = 0; i < m_lockCount; i++)
- JSLock::lock(m_lockingForReal);
+ m_globalData->apiLock().grabAllLocks(m_lockCount);
+}
+
+#else // (OS(DARWIN) || USE(PTHREADS))
- --lockDropDepth;
+GlobalJSLock::GlobalJSLock()
+{
+}
+
+GlobalJSLock::~GlobalJSLock()
+{
}
-#else
+JSLockHolder::JSLockHolder(JSGlobalData*)
+{
+}
+
+JSLockHolder::JSLockHolder(JSGlobalData&)
+{
+}
+
+JSLockHolder::JSLockHolder(ExecState*)
+{
+}
+
+JSLockHolder::~JSLockHolder()
+{
+}
-JSLock::JSLock(ExecState*)
- : m_lockingForReal(false)
+JSLock::JSLock()
{
}
-// If threading support is off, set the lock count to a constant value of 1 so ssertions
-// that the lock is held don't fail
-intptr_t JSLock::lockCount()
+JSLock::~JSLock()
{
- return 1;
}
bool JSLock::currentThreadIsHoldingLock()
return true;
}
-void JSLock::lock(bool)
+void JSLock::lock()
{
}
-void JSLock::unlock(bool)
+void JSLock::unlock()
{
}
{
}
+void JSLock::lock(JSGlobalData&)
+{
+}
+
+void JSLock::unlock(JSGlobalData&)
+{
+}
+
+unsigned JSLock::dropAllLocks()
+{
+ return 0;
+}
+
+unsigned JSLock::dropAllLocksUnconditionally()
+{
+ return 0;
+}
+
+void JSLock::grabAllLocks(unsigned)
+{
+}
+
JSLock::DropAllLocks::DropAllLocks(ExecState*)
{
}
-JSLock::DropAllLocks::DropAllLocks(bool)
+JSLock::DropAllLocks::DropAllLocks(JSGlobalData*)
{
}
{
}
-#endif // USE(MULTIPLE_THREADS)
+#endif // (OS(DARWIN) || USE(PTHREADS))
} // namespace JSC