| /* |
| * 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 |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the NU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA |
| * |
| */ |
| |
| #include "config.h" |
| #include "JSLock.h" |
| |
| #include "Heap.h" |
| #include "CallFrame.h" |
| #include "JSGlobalObject.h" |
| #include "JSObject.h" |
| #include "JSCInlines.h" |
| #include "SamplingProfiler.h" |
| #include <thread> |
| |
| namespace JSC { |
| |
| StaticLock GlobalJSLock::s_sharedInstanceMutex; |
| |
| GlobalJSLock::GlobalJSLock() |
| { |
| s_sharedInstanceMutex.lock(); |
| } |
| |
| GlobalJSLock::~GlobalJSLock() |
| { |
| s_sharedInstanceMutex.unlock(); |
| } |
| |
| JSLockHolder::JSLockHolder(ExecState* exec) |
| : m_vm(&exec->vm()) |
| { |
| init(); |
| } |
| |
| JSLockHolder::JSLockHolder(VM* vm) |
| : m_vm(vm) |
| { |
| init(); |
| } |
| |
| JSLockHolder::JSLockHolder(VM& vm) |
| : m_vm(&vm) |
| { |
| init(); |
| } |
| |
| void JSLockHolder::init() |
| { |
| m_vm->apiLock().lock(); |
| } |
| |
| JSLockHolder::~JSLockHolder() |
| { |
| RefPtr<JSLock> apiLock(&m_vm->apiLock()); |
| m_vm = nullptr; |
| apiLock->unlock(); |
| } |
| |
| JSLock::JSLock(VM* vm) |
| : m_ownerThreadID(std::thread::id()) |
| , m_lockCount(0) |
| , m_lockDropDepth(0) |
| , m_hasExclusiveThread(false) |
| , m_vm(vm) |
| , m_entryAtomicStringTable(nullptr) |
| { |
| } |
| |
| JSLock::~JSLock() |
| { |
| } |
| |
| void JSLock::willDestroyVM(VM* vm) |
| { |
| ASSERT_UNUSED(vm, m_vm == vm); |
| m_vm = nullptr; |
| } |
| |
| void JSLock::setExclusiveThread(std::thread::id threadId) |
| { |
| RELEASE_ASSERT(!m_lockCount && m_ownerThreadID == std::thread::id()); |
| m_hasExclusiveThread = (threadId != std::thread::id()); |
| m_ownerThreadID = threadId; |
| } |
| |
| void JSLock::lock() |
| { |
| lock(1); |
| } |
| |
| void JSLock::lock(intptr_t lockCount) |
| { |
| ASSERT(lockCount > 0); |
| if (currentThreadIsHoldingLock()) { |
| m_lockCount += lockCount; |
| return; |
| } |
| |
| if (!m_hasExclusiveThread) { |
| m_lock.lock(); |
| m_ownerThreadID = std::this_thread::get_id(); |
| } |
| ASSERT(!m_lockCount); |
| m_lockCount = lockCount; |
| |
| didAcquireLock(); |
| } |
| |
| void JSLock::didAcquireLock() |
| { |
| // FIXME: What should happen to the per-thread identifier table if we don't have a VM? |
| if (!m_vm) |
| return; |
| |
| 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_vm->heap.machineThreads().addCurrentThread(); |
| |
| #if ENABLE(SAMPLING_PROFILER) |
| // Note: this must come after addCurrentThread(). |
| if (SamplingProfiler* samplingProfiler = m_vm->samplingProfiler()) |
| samplingProfiler->noticeJSLockAcquisition(); |
| #endif |
| } |
| |
| void JSLock::unlock() |
| { |
| unlock(1); |
| } |
| |
| 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(); |
| } |
| } |
| } |
| |
| void JSLock::willReleaseLock() |
| { |
| RefPtr<VM> vm = m_vm; |
| if (vm) { |
| vm->drainMicrotasks(); |
| |
| vm->heap.releaseDelayedReleasedObjects(); |
| vm->setStackPointerAtVMEntry(nullptr); |
| } |
| |
| if (m_entryAtomicStringTable) { |
| wtfThreadData().setCurrentAtomicStringTable(m_entryAtomicStringTable); |
| m_entryAtomicStringTable = nullptr; |
| } |
| } |
| |
| void JSLock::lock(ExecState* exec) |
| { |
| exec->vm().apiLock().lock(); |
| } |
| |
| void JSLock::unlock(ExecState* exec) |
| { |
| exec->vm().apiLock().unlock(); |
| } |
| |
| 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(); |
| } |
| |
| // 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::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()); |
| } |
| |
| 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; |
| RELEASE_ASSERT(!m_vm->apiLock().currentThreadIsHoldingLock() || !m_vm->isCollectorBusyOnCurrentThread()); |
| m_droppedLockCount = m_vm->apiLock().dropAllLocks(this); |
| } |
| |
| JSLock::DropAllLocks::DropAllLocks(ExecState* exec) |
| : DropAllLocks(exec ? &exec->vm() : nullptr) |
| { |
| } |
| |
| JSLock::DropAllLocks::DropAllLocks(VM& vm) |
| : DropAllLocks(&vm) |
| { |
| } |
| |
| JSLock::DropAllLocks::~DropAllLocks() |
| { |
| if (!m_vm) |
| return; |
| m_vm->apiLock().grabAllLocks(this, m_droppedLockCount); |
| } |
| |
| } // namespace JSC |