X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/4e4e5a6f2694187498445a6ac6f1634ce8141119..217a6308cd6a1dc049a0bb69263bd4c91f91c4d0:/runtime/JSLock.cpp diff --git a/runtime/JSLock.cpp b/runtime/JSLock.cpp index a1cffbd..843d7fc 100644 --- a/runtime/JSLock.cpp +++ b/runtime/JSLock.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2005, 2008, 2012 Apple Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -21,102 +21,129 @@ #include "config.h" #include "JSLock.h" -#include "Collector.h" +#include "Heap.h" #include "CallFrame.h" +#include "JSGlobalObject.h" +#include "JSObject.h" +#include "Operations.h" -#if ENABLE(JSC_MULTIPLE_THREADS) +#if USE(PTHREADS) #include #endif namespace JSC { -#if ENABLE(JSC_MULTIPLE_THREADS) +Mutex* GlobalJSLock::s_sharedInstanceLock = 0; -// Acquire this mutex before accessing lock-related data. -static pthread_mutex_t JSMutex = PTHREAD_MUTEX_INITIALIZER; +GlobalJSLock::GlobalJSLock() +{ + s_sharedInstanceLock->lock(); +} -// 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() +{ + s_sharedInstanceLock->unlock(); +} -static void createJSLockCount() +void GlobalJSLock::initialize() { - pthread_key_create(&JSLockCount, 0); + s_sharedInstanceLock = new Mutex(); } -pthread_once_t createJSLockCountOnce = PTHREAD_ONCE_INIT; +JSLockHolder::JSLockHolder(ExecState* exec) + : m_vm(&exec->vm()) +{ + init(); +} -// Lock nesting count. -intptr_t JSLock::lockCount() +JSLockHolder::JSLockHolder(VM* vm) + : m_vm(vm) { - pthread_once(&createJSLockCountOnce, createJSLockCount); + init(); +} - return reinterpret_cast(pthread_getspecific(JSLockCount)); +JSLockHolder::JSLockHolder(VM& vm) + : m_vm(&vm) +{ + init(); } -static void setLockCount(intptr_t count) +void JSLockHolder::init() { - ASSERT(count >= 0); - pthread_setspecific(JSLockCount, reinterpret_cast(count)); + m_vm->apiLock().lock(); } -JSLock::JSLock(ExecState* exec) - : m_lockBehavior(exec->globalData().isSharedInstance() ? LockForReal : SilenceAssertionsOnly) +JSLockHolder::~JSLockHolder() { - lock(m_lockBehavior); + RefPtr apiLock(&m_vm->apiLock()); + m_vm.clear(); + apiLock->unlock(); } -void JSLock::lock(JSLockBehavior lockBehavior) +JSLock::JSLock(VM* vm) + : m_ownerThread(0) + , m_lockCount(0) + , m_lockDropDepth(0) + , m_vm(vm) { -#ifdef NDEBUG - // Locking "not for real" is a debug-only feature. - if (lockBehavior == SilenceAssertionsOnly) - return; -#endif + m_spinLock.Init(); +} - pthread_once(&createJSLockCountOnce, createJSLockCount); +JSLock::~JSLock() +{ +} - intptr_t currentLockCount = lockCount(); - if (!currentLockCount && lockBehavior == LockForReal) { - int result; - result = pthread_mutex_lock(&JSMutex); - ASSERT(!result); - } - setLockCount(currentLockCount + 1); +void JSLock::willDestroyVM(VM* vm) +{ + ASSERT_UNUSED(vm, m_vm == vm); + m_vm = 0; } -void JSLock::unlock(JSLockBehavior lockBehavior) +void JSLock::lock() { - ASSERT(lockCount()); + ThreadIdentifier currentThread = WTF::currentThread(); + { + SpinLockHolder holder(&m_spinLock); + if (m_ownerThread == currentThread && m_lockCount) { + m_lockCount++; + return; + } + } -#ifdef NDEBUG - // Locking "not for real" is a debug-only feature. - if (lockBehavior == SilenceAssertionsOnly) - return; -#endif + m_lock.lock(); - intptr_t newLockCount = lockCount() - 1; - setLockCount(newLockCount); - if (!newLockCount && lockBehavior == LockForReal) { - int result; - result = pthread_mutex_unlock(&JSMutex); - ASSERT(!result); + { + SpinLockHolder holder(&m_spinLock); + m_ownerThread = currentThread; + ASSERT(!m_lockCount); + m_lockCount = 1; } } +void JSLock::unlock() +{ + SpinLockHolder holder(&m_spinLock); + ASSERT(currentThreadIsHoldingLock()); + + m_lockCount--; + + if (!m_lockCount) + m_lock.unlock(); +} + void JSLock::lock(ExecState* exec) { - lock(exec->globalData().isSharedInstance() ? LockForReal : SilenceAssertionsOnly); + exec->vm().apiLock().lock(); } void JSLock::unlock(ExecState* exec) { - unlock(exec->globalData().isSharedInstance() ? LockForReal : SilenceAssertionsOnly); + exec->vm().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 @@ -129,18 +156,18 @@ bool JSLock::currentThreadIsHoldingLock() // context if the thread leaves JSC by making a call out to an external // function through a callback. // -// All threads using the context share the same JS stack (the RegisterFile). -// Whenever a thread calls into JSC it starts using the RegisterFile from the +// All threads using the context share the same JS stack (the JSStack). +// Whenever a thread calls into JSC it starts using the JSStack from the // previous 'high water mark' - the maximum point the stack has ever grown to -// (returned by RegisterFile::end()). So if a first thread calls out to a +// (returned by JSStack::end()). So if a first thread calls out to a // callback, and a second thread enters JSC, then also exits by calling out // to a callback, we can be left with stackframes from both threads in the -// RegisterFile. As such, a problem may occur should the first thread's +// JSStack. As such, a problem may occur should the first thread's // callback complete first, and attempt to return to JSC. Were we to allow // 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 @@ -149,7 +176,7 @@ bool JSLock::currentThreadIsHoldingLock() // 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). // @@ -159,96 +186,151 @@ bool JSLock::currentThreadIsHoldingLock() // 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_lockBehavior(exec->globalData().isSharedInstance() ? LockForReal : SilenceAssertionsOnly) +// This function returns the number of locks that were dropped. +unsigned JSLock::dropAllLocks(SpinLock& spinLock) { - pthread_once(&createJSLockCountOnce, createJSLockCount); - - if (lockDropDepth++) { - m_lockCount = 0; - return; - } +#if PLATFORM(IOS) + ASSERT_UNUSED(spinLock, spinLock.IsHeld()); + // Check if this thread is currently holding the lock. + // FIXME: Maybe we want to require this, guard with an ASSERT? + unsigned lockCount = m_lockCount; + if (!lockCount || m_ownerThread != WTF::currentThread()) + return 0; + + // 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; +#else + UNUSED_PARAM(spinLock); + if (m_lockDropDepth++) + return 0; - m_lockCount = JSLock::lockCount(); - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::unlock(m_lockBehavior); + return dropAllLocksUnconditionally(spinLock); +#endif } -JSLock::DropAllLocks::DropAllLocks(JSLockBehavior JSLockBehavior) - : m_lockBehavior(JSLockBehavior) +unsigned JSLock::dropAllLocksUnconditionally(SpinLock& spinLock) { - pthread_once(&createJSLockCountOnce, createJSLockCount); - - if (lockDropDepth++) { - m_lockCount = 0; - return; - } - - // It is necessary to drop even "unreal" locks, because having a non-zero lock count - // will prevent a real lock from being taken. +#if PLATFORM(IOS) + ASSERT_UNUSED(spinLock, spinLock.IsHeld()); + unsigned lockCount; + // Check if this thread is currently holding the lock. + // FIXME: Maybe we want to require this, guard with an ASSERT? + 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; +#else + UNUSED_PARAM(spinLock); + unsigned lockCount = m_lockCount; + for (unsigned i = 0; i < lockCount; i++) + unlock(); - m_lockCount = JSLock::lockCount(); - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::unlock(m_lockBehavior); + return lockCount; +#endif } -JSLock::DropAllLocks::~DropAllLocks() +void JSLock::grabAllLocks(unsigned lockCount, SpinLock& spinLock) { - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::lock(m_lockBehavior); - - --lockDropDepth; -} +#if PLATFORM(IOS) + ASSERT(spinLock.IsHeld()); + // If no locks were dropped, nothing to do! + if (!lockCount) + return; -#else + 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? + if (m_ownerThread == currentThread && m_lockCount) { + m_lockCount += lockCount; + m_lockDropDepth--; + return; + } -JSLock::JSLock(ExecState*) - : m_lockBehavior(SilenceAssertionsOnly) -{ -} + spinLock.Unlock(); + m_lock.lock(); + spinLock.Lock(); -// 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() -{ - return 1; -} - -bool JSLock::currentThreadIsHoldingLock() -{ - return true; -} + m_ownerThread = currentThread; + ASSERT(!m_lockCount); + m_lockCount = lockCount; + m_lockDropDepth--; +#else + UNUSED_PARAM(spinLock); + for (unsigned i = 0; i < lockCount; i++) + lock(); -void JSLock::lock(JSLockBehavior) -{ + m_lockDropDepth--; +#endif } -void JSLock::unlock(JSLockBehavior) +#if PLATFORM(IOS) +JSLock::DropAllLocks::DropAllLocks(ExecState* exec, AlwaysDropLocksTag alwaysDropLocks) + : m_lockCount(0) + , m_vm(&exec->vm()) { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + SpinLockHolder holder(&spinLock); + if (alwaysDropLocks) + m_lockCount = m_vm->apiLock().dropAllLocksUnconditionally(spinLock); + else + m_lockCount = m_vm->apiLock().dropAllLocks(spinLock); } -void JSLock::lock(ExecState*) +JSLock::DropAllLocks::DropAllLocks(VM* vm, AlwaysDropLocksTag alwaysDropLocks) + : m_lockCount(0) + , m_vm(vm) { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + SpinLockHolder holder(&spinLock); + if (alwaysDropLocks) + m_lockCount = m_vm->apiLock().dropAllLocksUnconditionally(spinLock); + else + m_lockCount = m_vm->apiLock().dropAllLocks(spinLock); } -void JSLock::unlock(ExecState*) +JSLock::DropAllLocks::~DropAllLocks() { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + SpinLockHolder holder(&spinLock); + m_vm->apiLock().grabAllLocks(m_lockCount, spinLock); } - -JSLock::DropAllLocks::DropAllLocks(ExecState*) +#else +JSLock::DropAllLocks::DropAllLocks(ExecState* exec) + : m_lockCount(0) + , m_vm(&exec->vm()) { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + m_lockCount = m_vm->apiLock().dropAllLocks(spinLock); } -JSLock::DropAllLocks::DropAllLocks(JSLockBehavior) +JSLock::DropAllLocks::DropAllLocks(VM* vm) + : m_lockCount(0) + , m_vm(vm) { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + m_lockCount = m_vm->apiLock().dropAllLocks(spinLock); } JSLock::DropAllLocks::~DropAllLocks() { + SpinLock& spinLock = m_vm->apiLock().m_spinLock; + m_vm->apiLock().grabAllLocks(m_lockCount, spinLock); } - -#endif // USE(MULTIPLE_THREADS) +#endif } // namespace JSC