X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/9dae56ea45a0f5f8136a5c93d6f3a7f99399ca73..ed1e77d3adeb83d26fd1dfb16dd84cabdcefd250:/runtime/JSLock.cpp diff --git a/runtime/JSLock.cpp b/runtime/JSLock.cpp index 7ece5da..c2ee0a5 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, 2014 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,234 +21,268 @@ #include "config.h" #include "JSLock.h" -#include "Collector.h" +#include "Heap.h" #include "CallFrame.h" - -#if ENABLE(JSC_MULTIPLE_THREADS) -#include -#endif +#include "JSGlobalObject.h" +#include "JSObject.h" +#include "JSCInlines.h" +#include namespace JSC { -#if ENABLE(JSC_MULTIPLE_THREADS) - -// Acquire this mutex before accessing lock-related data. -static pthread_mutex_t JSMutex = PTHREAD_MUTEX_INITIALIZER; +std::mutex* GlobalJSLock::s_sharedInstanceMutex; -// Thread-specific key that tells whether a thread holds the JSMutex, and how many times it was taken recursively. -pthread_key_t JSLockCount; - -static void createJSLockCount() +GlobalJSLock::GlobalJSLock() { - pthread_key_create(&JSLockCount, 0); + s_sharedInstanceMutex->lock(); } -pthread_once_t createJSLockCountOnce = PTHREAD_ONCE_INIT; - -// Lock nesting count. -intptr_t JSLock::lockCount() +GlobalJSLock::~GlobalJSLock() { - pthread_once(&createJSLockCountOnce, createJSLockCount); - - return reinterpret_cast(pthread_getspecific(JSLockCount)); + s_sharedInstanceMutex->unlock(); } -static void setLockCount(intptr_t count) +void GlobalJSLock::initialize() { - ASSERT(count >= 0); - pthread_setspecific(JSLockCount, reinterpret_cast(count)); + s_sharedInstanceMutex = new std::mutex(); } -JSLock::JSLock(ExecState* exec) - : m_lockingForReal(exec->globalData().isSharedInstance) +JSLockHolder::JSLockHolder(ExecState* exec) + : m_vm(&exec->vm()) { - lock(m_lockingForReal); + init(); } -void JSLock::lock(bool lockForReal) +JSLockHolder::JSLockHolder(VM* vm) + : m_vm(vm) { -#ifdef NDEBUG - // Locking "not for real" is a debug-only feature. - if (!lockForReal) - return; -#endif + init(); +} - pthread_once(&createJSLockCountOnce, createJSLockCount); +JSLockHolder::JSLockHolder(VM& vm) + : m_vm(&vm) +{ + init(); +} - intptr_t currentLockCount = lockCount(); - if (!currentLockCount && lockForReal) { - int result; - result = pthread_mutex_lock(&JSMutex); - ASSERT(!result); - } - setLockCount(currentLockCount + 1); +void JSLockHolder::init() +{ + m_vm->apiLock().lock(); } -void JSLock::unlock(bool lockForReal) +JSLockHolder::~JSLockHolder() { - ASSERT(lockCount()); + RefPtr apiLock(&m_vm->apiLock()); + m_vm = nullptr; + apiLock->unlock(); +} -#ifdef NDEBUG - // Locking "not for real" is a debug-only feature. - if (!lockForReal) - return; -#endif - - intptr_t newLockCount = lockCount() - 1; - setLockCount(newLockCount); - if (!newLockCount && lockForReal) { - int result; - result = pthread_mutex_unlock(&JSMutex); - ASSERT(!result); - } +JSLock::JSLock(VM* vm) + : m_ownerThreadID(std::thread::id()) + , m_lockCount(0) + , m_lockDropDepth(0) + , m_hasExclusiveThread(false) + , m_vm(vm) + , m_entryAtomicStringTable(nullptr) +{ } -void JSLock::lock(ExecState* exec) +JSLock::~JSLock() { - lock(exec->globalData().isSharedInstance); } -void JSLock::unlock(ExecState* exec) +void JSLock::willDestroyVM(VM* vm) { - unlock(exec->globalData().isSharedInstance); + ASSERT_UNUSED(vm, m_vm == vm); + m_vm = nullptr; } -bool JSLock::currentThreadIsHoldingLock() +void JSLock::setExclusiveThread(std::thread::id threadId) { - pthread_once(&createJSLockCountOnce, createJSLockCount); - return !!pthread_getspecific(JSLockCount); -} - -// This is fairly nasty. We allow multiple threads to run on the same -// context, and we do not require any locking semantics in doing so - -// clients of the API may simply use the context from multiple threads -// concurently, and assume this will work. In order to make this work, -// We lock the context when a thread enters, and unlock it when it leaves. -// However we do not only unlock when the thread returns from its -// entry point (evaluate script or call function), we also unlock the -// 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 -// 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 -// 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 -// 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 -// 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 -// first time a thread exits it will call DropAllLocks - which will do as expected -// and drop locks allowing another thread to enter. Should another thread, or 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 -// callback, or by re-entering through another call to evaulate script or call -// function). -// -// This policy is slightly more restricive than it needs to be for correctness - -// we could validly allow futher entries into JSC from other threads, we only -// need ensure that callbacks return in the reverse chronological order of the -// order in which they were made - though implementing the less restrictive policy -// would likely increase complexity and overhead. -// -static unsigned lockDropDepth = 0; + RELEASE_ASSERT(!m_lockCount && m_ownerThreadID == std::thread::id()); + m_hasExclusiveThread = (threadId != std::thread::id()); + m_ownerThreadID = threadId; +} -JSLock::DropAllLocks::DropAllLocks(ExecState* exec) - : m_lockingForReal(exec->globalData().isSharedInstance) +void JSLock::lock() { - pthread_once(&createJSLockCountOnce, createJSLockCount); + lock(1); +} - if (lockDropDepth++) { - m_lockCount = 0; +void JSLock::lock(intptr_t lockCount) +{ + ASSERT(lockCount > 0); + if (currentThreadIsHoldingLock()) { + m_lockCount += lockCount; return; } - m_lockCount = JSLock::lockCount(); - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::unlock(m_lockingForReal); + if (!m_hasExclusiveThread) { + m_lock.lock(); + m_ownerThreadID = std::this_thread::get_id(); + } + ASSERT(!m_lockCount); + m_lockCount = lockCount; + + didAcquireLock(); } -JSLock::DropAllLocks::DropAllLocks(bool lockingForReal) - : m_lockingForReal(lockingForReal) +void JSLock::didAcquireLock() { - pthread_once(&createJSLockCountOnce, createJSLockCount); - - if (lockDropDepth++) { - m_lockCount = 0; + // FIXME: What should happen to the per-thread identifier table if we don't have a VM? + if (!m_vm) return; - } - // It is necessary to drop even "unreal" locks, because having a non-zero lock count - // will prevent a real lock from being taken. + RELEASE_ASSERT(!m_vm->stackPointerAtVMEntry()); + void* p = &p; // A proxy for the current stack pointer. + m_vm->setStackPointerAtVMEntry(p); + + WTFThreadData& threadData = wtfThreadData(); + m_vm->setLastStackTop(threadData.savedLastStackTop()); + + ASSERT(!m_entryAtomicStringTable); + m_entryAtomicStringTable = threadData.setCurrentAtomicStringTable(m_vm->atomicStringTable()); + ASSERT(m_entryAtomicStringTable); - m_lockCount = JSLock::lockCount(); - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::unlock(m_lockingForReal); + m_vm->heap.machineThreads().addCurrentThread(); } -JSLock::DropAllLocks::~DropAllLocks() +void JSLock::unlock() { - for (intptr_t i = 0; i < m_lockCount; i++) - JSLock::lock(m_lockingForReal); - - --lockDropDepth; + unlock(1); } -#else +void JSLock::unlock(intptr_t unlockCount) +{ + RELEASE_ASSERT(currentThreadIsHoldingLock()); + ASSERT(m_lockCount >= unlockCount); + + // Maintain m_lockCount while calling willReleaseLock() so that its callees know that + // they still have the lock. + if (unlockCount == m_lockCount) + willReleaseLock(); + + m_lockCount -= unlockCount; + + if (!m_lockCount) { + + if (!m_hasExclusiveThread) { + m_ownerThreadID = std::thread::id(); + m_lock.unlock(); + } + } +} -JSLock::JSLock(ExecState*) - : m_lockingForReal(false) +void JSLock::willReleaseLock() { + if (m_vm) { + m_vm->heap.releaseDelayedReleasedObjects(); + m_vm->setStackPointerAtVMEntry(nullptr); + } + + if (m_entryAtomicStringTable) { + wtfThreadData().setCurrentAtomicStringTable(m_entryAtomicStringTable); + m_entryAtomicStringTable = nullptr; + } } -// 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() +void JSLock::lock(ExecState* exec) { - return 1; + exec->vm().apiLock().lock(); } -bool JSLock::currentThreadIsHoldingLock() +void JSLock::unlock(ExecState* exec) { - return true; + exec->vm().apiLock().unlock(); } -void JSLock::lock(bool) +bool JSLock::currentThreadIsHoldingLock() { + ASSERT(!m_hasExclusiveThread || (exclusiveThread() == std::this_thread::get_id())); + if (m_hasExclusiveThread) + return !!m_lockCount; + return m_ownerThreadID == std::this_thread::get_id(); } -void JSLock::unlock(bool) +// This function returns the number of locks that were dropped. +unsigned JSLock::dropAllLocks(DropAllLocks* dropper) { + if (m_hasExclusiveThread) { + ASSERT(exclusiveThread() == std::this_thread::get_id()); + return 0; + } + + if (!currentThreadIsHoldingLock()) + return 0; + + ++m_lockDropDepth; + + dropper->setDropDepth(m_lockDropDepth); + + WTFThreadData& threadData = wtfThreadData(); + threadData.setSavedStackPointerAtVMEntry(m_vm->stackPointerAtVMEntry()); + threadData.setSavedLastStackTop(m_vm->lastStackTop()); + + unsigned droppedLockCount = m_lockCount; + unlock(droppedLockCount); + + return droppedLockCount; } -void JSLock::lock(ExecState*) +void JSLock::grabAllLocks(DropAllLocks* dropper, unsigned droppedLockCount) { + ASSERT(!m_hasExclusiveThread || !droppedLockCount); + + // If no locks were dropped, nothing to do! + if (!droppedLockCount) + return; + + ASSERT(!currentThreadIsHoldingLock()); + lock(droppedLockCount); + + while (dropper->dropDepth() != m_lockDropDepth) { + unlock(droppedLockCount); + std::this_thread::yield(); + lock(droppedLockCount); + } + + --m_lockDropDepth; + + WTFThreadData& threadData = wtfThreadData(); + m_vm->setStackPointerAtVMEntry(threadData.savedStackPointerAtVMEntry()); + m_vm->setLastStackTop(threadData.savedLastStackTop()); } -void JSLock::unlock(ExecState*) +JSLock::DropAllLocks::DropAllLocks(VM* vm) + : m_droppedLockCount(0) + // If the VM is in the middle of being destroyed then we don't want to resurrect it + // by allowing DropAllLocks to ref it. By this point the JSLock has already been + // released anyways, so it doesn't matter that DropAllLocks is a no-op. + , m_vm(vm->refCount() ? vm : nullptr) { + if (!m_vm) + return; + wtfThreadData().resetCurrentAtomicStringTable(); + RELEASE_ASSERT(!m_vm->apiLock().currentThreadIsHoldingLock() || !m_vm->isCollectorBusy()); + m_droppedLockCount = m_vm->apiLock().dropAllLocks(this); } -JSLock::DropAllLocks::DropAllLocks(ExecState*) +JSLock::DropAllLocks::DropAllLocks(ExecState* exec) + : DropAllLocks(exec ? &exec->vm() : nullptr) { } -JSLock::DropAllLocks::DropAllLocks(bool) +JSLock::DropAllLocks::DropAllLocks(VM& vm) + : DropAllLocks(&vm) { } JSLock::DropAllLocks::~DropAllLocks() { + if (!m_vm) + return; + m_vm->apiLock().grabAllLocks(this, m_droppedLockCount); + wtfThreadData().setCurrentAtomicStringTable(m_vm->atomicStringTable()); } -#endif // USE(MULTIPLE_THREADS) - } // namespace JSC