summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/heap/MachineStackMarker.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/heap/MachineStackMarker.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/JavaScriptCore/heap/MachineStackMarker.cpp')
-rw-r--r--Source/JavaScriptCore/heap/MachineStackMarker.cpp993
1 files changed, 798 insertions, 195 deletions
diff --git a/Source/JavaScriptCore/heap/MachineStackMarker.cpp b/Source/JavaScriptCore/heap/MachineStackMarker.cpp
index f546cb38b..4d4e8bb22 100644
--- a/Source/JavaScriptCore/heap/MachineStackMarker.cpp
+++ b/Source/JavaScriptCore/heap/MachineStackMarker.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
+ * Copyright (C) 2003-2017 Apple Inc. All rights reserved.
* Copyright (C) 2007 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Acision BV. All rights reserved.
*
@@ -23,11 +23,17 @@
#include "MachineStackMarker.h"
#include "ConservativeRoots.h"
+#include "GPRInfo.h"
#include "Heap.h"
#include "JSArray.h"
+#include "JSCInlines.h"
+#include "LLIntPCRanges.h"
+#include "MacroAssembler.h"
#include "VM.h"
#include <setjmp.h>
#include <stdlib.h>
+#include <wtf/MainThread.h>
+#include <wtf/NeverDestroyed.h>
#include <wtf/StdLibExtras.h>
#if OS(DARWIN)
@@ -60,88 +66,149 @@
#if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN)
#include <signal.h>
-#endif
-
-#endif
-using namespace WTF;
-
-namespace JSC {
+// We use SIGUSR2 to suspend and resume machine threads in JavaScriptCore.
+static const int SigThreadSuspendResume = SIGUSR2;
+static StaticLock globalSignalLock;
+thread_local static std::atomic<JSC::MachineThreads::ThreadData*> threadLocalCurrentThread { nullptr };
-static inline void swapIfBackwards(void*& begin, void*& end)
+static void pthreadSignalHandlerSuspendResume(int, siginfo_t*, void* ucontext)
{
-#if OS(WINCE)
- if (begin <= end)
+ // Touching thread local atomic types from signal handlers is allowed.
+ JSC::MachineThreads::ThreadData* threadData = threadLocalCurrentThread.load();
+
+ if (threadData->suspended.load(std::memory_order_acquire)) {
+ // This is signal handler invocation that is intended to be used to resume sigsuspend.
+ // So this handler invocation itself should not process.
+ //
+ // When signal comes, first, the system calls signal handler. And later, sigsuspend will be resumed. Signal handler invocation always precedes.
+ // So, the problem never happens that suspended.store(true, ...) will be executed before the handler is called.
+ // http://pubs.opengroup.org/onlinepubs/009695399/functions/sigsuspend.html
return;
- std::swap(begin, end);
+ }
+
+ ucontext_t* userContext = static_cast<ucontext_t*>(ucontext);
+#if CPU(PPC)
+ threadData->suspendedMachineContext = *userContext->uc_mcontext.uc_regs;
#else
-UNUSED_PARAM(begin);
-UNUSED_PARAM(end);
+ threadData->suspendedMachineContext = userContext->uc_mcontext;
#endif
-}
-#if OS(DARWIN)
-typedef mach_port_t PlatformThread;
-#elif OS(WINDOWS)
-typedef HANDLE PlatformThread;
-#elif USE(PTHREADS)
-typedef pthread_t PlatformThread;
-static const int SigThreadSuspendResume = SIGUSR2;
-
-#if defined(SA_RESTART)
-static void pthreadSignalHandlerSuspendResume(int)
-{
- sigset_t signalSet;
- sigemptyset(&signalSet);
- sigaddset(&signalSet, SigThreadSuspendResume);
- sigsuspend(&signalSet);
+ // Allow suspend caller to see that this thread is suspended.
+ // sem_post is async-signal-safe function. It means that we can call this from a signal handler.
+ // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html#tag_02_04_03
+ //
+ // And sem_post emits memory barrier that ensures that suspendedMachineContext is correctly saved.
+ // http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11
+ sem_post(&threadData->semaphoreForSuspendResume);
+
+ // Reaching here, SigThreadSuspendResume is blocked in this handler (this is configured by sigaction's sa_mask).
+ // So before calling sigsuspend, SigThreadSuspendResume to this thread is deferred. This ensures that the handler is not executed recursively.
+ sigset_t blockedSignalSet;
+ sigfillset(&blockedSignalSet);
+ sigdelset(&blockedSignalSet, SigThreadSuspendResume);
+ sigsuspend(&blockedSignalSet);
+
+ // Allow resume caller to see that this thread is resumed.
+ sem_post(&threadData->semaphoreForSuspendResume);
}
+#endif // USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN)
+
#endif
-#endif
-class MachineThreads::Thread {
- WTF_MAKE_FAST_ALLOCATED;
+using namespace WTF;
+
+namespace JSC {
+
+using Thread = MachineThreads::Thread;
+
+class ActiveMachineThreadsManager;
+static ActiveMachineThreadsManager& activeMachineThreadsManager();
+
+class ActiveMachineThreadsManager {
+ WTF_MAKE_NONCOPYABLE(ActiveMachineThreadsManager);
public:
- Thread(const PlatformThread& platThread, void* base)
- : platformThread(platThread)
- , stackBase(base)
+
+ class Locker {
+ public:
+ Locker(ActiveMachineThreadsManager& manager)
+ : m_locker(manager.m_lock)
+ {
+ }
+
+ private:
+ LockHolder m_locker;
+ };
+
+ void add(MachineThreads* machineThreads)
{
-#if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN) && defined(SA_RESTART)
- // if we have SA_RESTART, enable SIGUSR2 debugging mechanism
- struct sigaction action;
- action.sa_handler = pthreadSignalHandlerSuspendResume;
- sigemptyset(&action.sa_mask);
- action.sa_flags = SA_RESTART;
- sigaction(SigThreadSuspendResume, &action, 0);
+ LockHolder managerLock(m_lock);
+ m_set.add(machineThreads);
+ }
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SigThreadSuspendResume);
- pthread_sigmask(SIG_UNBLOCK, &mask, 0);
-#endif
+ void THREAD_SPECIFIC_CALL remove(MachineThreads* machineThreads)
+ {
+ LockHolder managerLock(m_lock);
+ auto recordedMachineThreads = m_set.take(machineThreads);
+ RELEASE_ASSERT(recordedMachineThreads == machineThreads);
}
- Thread* next;
- PlatformThread platformThread;
- void* stackBase;
+ bool contains(MachineThreads* machineThreads)
+ {
+ return m_set.contains(machineThreads);
+ }
+
+private:
+ typedef HashSet<MachineThreads*> MachineThreadsSet;
+
+ ActiveMachineThreadsManager() { }
+
+ Lock m_lock;
+ MachineThreadsSet m_set;
+
+ friend ActiveMachineThreadsManager& activeMachineThreadsManager();
};
+static ActiveMachineThreadsManager& activeMachineThreadsManager()
+{
+ static std::once_flag initializeManagerOnceFlag;
+ static ActiveMachineThreadsManager* manager = nullptr;
+
+ std::call_once(initializeManagerOnceFlag, [] {
+ manager = new ActiveMachineThreadsManager();
+ });
+ return *manager;
+}
+
+static inline PlatformThread getCurrentPlatformThread()
+{
+#if OS(DARWIN)
+ return pthread_mach_thread_np(pthread_self());
+#elif OS(WINDOWS)
+ return GetCurrentThreadId();
+#elif USE(PTHREADS)
+ return pthread_self();
+#endif
+}
+
MachineThreads::MachineThreads(Heap* heap)
: m_registeredThreads(0)
- , m_threadSpecific(0)
+ , m_threadSpecificForMachineThreads(0)
#if !ASSERT_DISABLED
, m_heap(heap)
#endif
{
UNUSED_PARAM(heap);
+ threadSpecificKeyCreate(&m_threadSpecificForMachineThreads, removeThread);
+ activeMachineThreadsManager().add(this);
}
MachineThreads::~MachineThreads()
{
- if (m_threadSpecific)
- threadSpecificKeyDelete(m_threadSpecific);
+ activeMachineThreadsManager().remove(this);
+ threadSpecificKeyDelete(m_threadSpecificForMachineThreads);
- MutexLocker registeredThreadsLock(m_registeredThreadsMutex);
+ LockHolder registeredThreadsLock(m_registeredThreadsMutex);
for (Thread* t = m_registeredThreads; t;) {
Thread* next = t->next;
delete t;
@@ -149,171 +216,247 @@ MachineThreads::~MachineThreads()
}
}
-static inline PlatformThread getCurrentPlatformThread()
+static MachineThreads::ThreadData* threadData()
{
-#if OS(DARWIN)
- return pthread_mach_thread_np(pthread_self());
-#elif OS(WINDOWS)
- return GetCurrentThread();
-#elif USE(PTHREADS)
- return pthread_self();
-#endif
+ static NeverDestroyed<ThreadSpecific<MachineThreads::ThreadData, CanBeGCThread::True>> threadData;
+ return threadData.get();
+}
+
+MachineThreads::Thread::Thread(ThreadData* threadData)
+ : data(threadData)
+{
+ ASSERT(threadData);
}
-static inline bool equalThread(const PlatformThread& first, const PlatformThread& second)
+Thread* MachineThreads::Thread::createForCurrentThread()
+{
+ return new Thread(threadData());
+}
+
+bool MachineThreads::Thread::operator==(const PlatformThread& other) const
{
#if OS(DARWIN) || OS(WINDOWS)
- return first == second;
+ return data->platformThread == other;
#elif USE(PTHREADS)
- return !!pthread_equal(first, second);
+ return !!pthread_equal(data->platformThread, other);
#else
#error Need a way to compare threads on this platform
#endif
}
-void MachineThreads::makeUsableFromMultipleThreads()
-{
- if (m_threadSpecific)
- return;
-
- threadSpecificKeyCreate(&m_threadSpecific, removeThread);
-}
-
void MachineThreads::addCurrentThread()
{
- ASSERT(!m_heap->vm()->exclusiveThread || m_heap->vm()->exclusiveThread == currentThread());
+ ASSERT(!m_heap->vm()->hasExclusiveThread() || m_heap->vm()->exclusiveThread() == std::this_thread::get_id());
- if (!m_threadSpecific || threadSpecificGet(m_threadSpecific))
+ if (threadSpecificGet(m_threadSpecificForMachineThreads)) {
+#ifndef NDEBUG
+ LockHolder lock(m_registeredThreadsMutex);
+ ASSERT(threadSpecificGet(m_threadSpecificForMachineThreads) == this);
+#endif
return;
+ }
- threadSpecificSet(m_threadSpecific, this);
- Thread* thread = new Thread(getCurrentPlatformThread(), wtfThreadData().stack().origin());
+ Thread* thread = Thread::createForCurrentThread();
+ threadSpecificSet(m_threadSpecificForMachineThreads, this);
- MutexLocker lock(m_registeredThreadsMutex);
+ LockHolder lock(m_registeredThreadsMutex);
thread->next = m_registeredThreads;
m_registeredThreads = thread;
}
-void MachineThreads::removeThread(void* p)
+Thread* MachineThreads::machineThreadForCurrentThread()
{
- if (p)
- static_cast<MachineThreads*>(p)->removeCurrentThread();
+ LockHolder lock(m_registeredThreadsMutex);
+ PlatformThread platformThread = getCurrentPlatformThread();
+ for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
+ if (*thread == platformThread)
+ return thread;
+ }
+
+ RELEASE_ASSERT_NOT_REACHED();
+ return nullptr;
}
-void MachineThreads::removeCurrentThread()
+void THREAD_SPECIFIC_CALL MachineThreads::removeThread(void* p)
{
- PlatformThread currentPlatformThread = getCurrentPlatformThread();
+ auto& manager = activeMachineThreadsManager();
+ ActiveMachineThreadsManager::Locker lock(manager);
+ auto machineThreads = static_cast<MachineThreads*>(p);
+ if (manager.contains(machineThreads)) {
+ // There's a chance that the MachineThreads registry that this thread
+ // was registered with was already destructed, and another one happened
+ // to be instantiated at the same address. Hence, this thread may or
+ // may not be found in this MachineThreads registry. We only need to
+ // do a removal if this thread is found in it.
+
+#if PLATFORM(WIN)
+ // On Windows the thread specific destructor is also called when the
+ // main thread is exiting. This may lead to the main thread waiting
+ // forever for the machine thread lock when exiting, if the sampling
+ // profiler thread was terminated by the system while holding the
+ // machine thread lock.
+ if (WTF::isMainThread())
+ return;
+#endif
- MutexLocker lock(m_registeredThreadsMutex);
+ machineThreads->removeThreadIfFound(getCurrentPlatformThread());
+ }
+}
- if (equalThread(currentPlatformThread, m_registeredThreads->platformThread)) {
- Thread* t = m_registeredThreads;
+template<typename PlatformThread>
+void MachineThreads::removeThreadIfFound(PlatformThread platformThread)
+{
+ LockHolder lock(m_registeredThreadsMutex);
+ Thread* t = m_registeredThreads;
+ if (*t == platformThread) {
m_registeredThreads = m_registeredThreads->next;
delete t;
} else {
Thread* last = m_registeredThreads;
- Thread* t;
for (t = m_registeredThreads->next; t; t = t->next) {
- if (equalThread(t->platformThread, currentPlatformThread)) {
+ if (*t == platformThread) {
last->next = t->next;
break;
}
last = t;
}
- ASSERT(t); // If t is NULL, we never found ourselves in the list.
delete t;
}
}
-#if COMPILER(GCC)
-#define REGISTER_BUFFER_ALIGNMENT __attribute__ ((aligned (sizeof(void*))))
-#else
-#define REGISTER_BUFFER_ALIGNMENT
-#endif
+SUPPRESS_ASAN
+void MachineThreads::gatherFromCurrentThread(ConservativeRoots& conservativeRoots, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks, CurrentThreadState& currentThreadState)
+{
+ if (currentThreadState.registerState) {
+ void* registersBegin = currentThreadState.registerState;
+ void* registersEnd = reinterpret_cast<void*>(roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(currentThreadState.registerState + 1)));
+ conservativeRoots.add(registersBegin, registersEnd, jitStubRoutines, codeBlocks);
+ }
-void MachineThreads::gatherFromCurrentThread(ConservativeRoots& conservativeRoots, void* stackCurrent)
+ conservativeRoots.add(currentThreadState.stackTop, currentThreadState.stackOrigin, jitStubRoutines, codeBlocks);
+}
+
+MachineThreads::ThreadData::ThreadData()
{
- // setjmp forces volatile registers onto the stack
- jmp_buf registers REGISTER_BUFFER_ALIGNMENT;
-#if COMPILER(MSVC)
-#pragma warning(push)
-#pragma warning(disable: 4611)
-#endif
- setjmp(registers);
-#if COMPILER(MSVC)
-#pragma warning(pop)
-#endif
+ auto stackBounds = wtfThreadData().stack();
+ platformThread = getCurrentPlatformThread();
+ stackBase = stackBounds.origin();
+ stackEnd = stackBounds.end();
+
+#if OS(WINDOWS)
+ ASSERT(platformThread == GetCurrentThreadId());
+ bool isSuccessful =
+ DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(),
+ &platformThreadHandle, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ RELEASE_ASSERT(isSuccessful);
+#elif USE(PTHREADS) && !OS(DARWIN)
+ threadLocalCurrentThread.store(this);
+
+ // Signal handlers are process global configuration.
+ static std::once_flag initializeSignalHandler;
+ std::call_once(initializeSignalHandler, [] {
+ // Intentionally block SigThreadSuspendResume in the handler.
+ // SigThreadSuspendResume will be allowed in the handler by sigsuspend.
+ struct sigaction action;
+ sigemptyset(&action.sa_mask);
+ sigaddset(&action.sa_mask, SigThreadSuspendResume);
+
+ action.sa_sigaction = pthreadSignalHandlerSuspendResume;
+ action.sa_flags = SA_RESTART | SA_SIGINFO;
+ sigaction(SigThreadSuspendResume, &action, 0);
+ });
- void* registersBegin = &registers;
- void* registersEnd = reinterpret_cast<void*>(roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(&registers + 1)));
- swapIfBackwards(registersBegin, registersEnd);
- conservativeRoots.add(registersBegin, registersEnd);
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SigThreadSuspendResume);
+ pthread_sigmask(SIG_UNBLOCK, &mask, 0);
- void* stackBegin = stackCurrent;
- void* stackEnd = wtfThreadData().stack().origin();
- swapIfBackwards(stackBegin, stackEnd);
- conservativeRoots.add(stackBegin, stackEnd);
+ sem_init(&semaphoreForSuspendResume, /* Only available in this process. */ 0, /* Initial value for the semaphore. */ 0);
+#endif
+}
+
+MachineThreads::ThreadData::~ThreadData()
+{
+#if OS(WINDOWS)
+ CloseHandle(platformThreadHandle);
+#elif USE(PTHREADS) && !OS(DARWIN)
+ sem_destroy(&semaphoreForSuspendResume);
+#endif
}
-static inline void suspendThread(const PlatformThread& platformThread)
+bool MachineThreads::ThreadData::suspend()
{
#if OS(DARWIN)
- thread_suspend(platformThread);
+ kern_return_t result = thread_suspend(platformThread);
+ return result == KERN_SUCCESS;
#elif OS(WINDOWS)
- SuspendThread(platformThread);
+ bool threadIsSuspended = (SuspendThread(platformThreadHandle) != (DWORD)-1);
+ ASSERT(threadIsSuspended);
+ return threadIsSuspended;
#elif USE(PTHREADS)
- pthread_kill(platformThread, SigThreadSuspendResume);
+ ASSERT_WITH_MESSAGE(getCurrentPlatformThread() != platformThread, "Currently we don't support suspend the current thread itself.");
+ {
+ // During suspend, suspend or resume should not be executed from the other threads.
+ // We use global lock instead of per thread lock.
+ // Consider the following case, there are threads A and B.
+ // And A attempt to suspend B and B attempt to suspend A.
+ // A and B send signals. And later, signals are delivered to A and B.
+ // In that case, both will be suspended.
+ LockHolder lock(globalSignalLock);
+ if (!suspendCount) {
+ // Ideally, we would like to use pthread_sigqueue. It allows us to pass the argument to the signal handler.
+ // But it can be used in a few platforms, like Linux.
+ // Instead, we use Thread* stored in the thread local storage to pass it to the signal handler.
+ if (pthread_kill(platformThread, SigThreadSuspendResume) == ESRCH)
+ return false;
+ sem_wait(&semaphoreForSuspendResume);
+ // Release barrier ensures that this operation is always executed after all the above processing is done.
+ suspended.store(true, std::memory_order_release);
+ }
+ ++suspendCount;
+ }
+ return true;
#else
#error Need a way to suspend threads on this platform
#endif
}
-static inline void resumeThread(const PlatformThread& platformThread)
+void MachineThreads::ThreadData::resume()
{
#if OS(DARWIN)
thread_resume(platformThread);
#elif OS(WINDOWS)
- ResumeThread(platformThread);
+ ResumeThread(platformThreadHandle);
#elif USE(PTHREADS)
- pthread_kill(platformThread, SigThreadSuspendResume);
+ {
+ // During resume, suspend or resume should not be executed from the other threads.
+ LockHolder lock(globalSignalLock);
+ if (suspendCount == 1) {
+ // When allowing SigThreadSuspendResume interrupt in the signal handler by sigsuspend and SigThreadSuspendResume is actually issued,
+ // the signal handler itself will be called once again.
+ // There are several ways to distinguish the handler invocation for suspend and resume.
+ // 1. Use different signal numbers. And check the signal number in the handler.
+ // 2. Use some arguments to distinguish suspend and resume in the handler. If pthread_sigqueue can be used, we can take this.
+ // 3. Use thread local storage with atomic variables in the signal handler.
+ // In this implementaiton, we take (3). suspended flag is used to distinguish it.
+ if (pthread_kill(platformThread, SigThreadSuspendResume) == ESRCH)
+ return;
+ sem_wait(&semaphoreForSuspendResume);
+ // Release barrier ensures that this operation is always executed after all the above processing is done.
+ suspended.store(false, std::memory_order_release);
+ }
+ --suspendCount;
+ }
#else
#error Need a way to resume threads on this platform
#endif
}
-typedef unsigned long usword_t; // word size, assumed to be either 32 or 64 bit
-
-#if OS(DARWIN)
-
-#if CPU(X86)
-typedef i386_thread_state_t PlatformThreadRegisters;
-#elif CPU(X86_64)
-typedef x86_thread_state64_t PlatformThreadRegisters;
-#elif CPU(PPC)
-typedef ppc_thread_state_t PlatformThreadRegisters;
-#elif CPU(PPC64)
-typedef ppc_thread_state64_t PlatformThreadRegisters;
-#elif CPU(ARM)
-typedef arm_thread_state_t PlatformThreadRegisters;
-#elif CPU(ARM64)
-typedef arm_thread_state64_t PlatformThreadRegisters;
-#else
-#error Unknown Architecture
-#endif
-
-#elif OS(WINDOWS)
-typedef CONTEXT PlatformThreadRegisters;
-#elif USE(PTHREADS)
-typedef pthread_attr_t PlatformThreadRegisters;
-#else
-#error Need a thread register struct for this platform
-#endif
-
-static size_t getPlatformThreadRegisters(const PlatformThread& platformThread, PlatformThreadRegisters& regs)
+size_t MachineThreads::ThreadData::getRegisters(ThreadData::Registers& registers)
{
+ ThreadData::Registers::PlatformRegisters& regs = registers.regs;
#if OS(DARWIN)
-
#if CPU(X86)
unsigned user_count = sizeof(regs)/sizeof(int);
thread_state_flavor_t flavor = i386_THREAD_STATE;
@@ -342,29 +485,32 @@ static size_t getPlatformThreadRegisters(const PlatformThread& platformThread, P
"JavaScript garbage collection failed because thread_get_state returned an error (%d). This is probably the result of running inside Rosetta, which is not supported.", result);
CRASH();
}
- return user_count * sizeof(usword_t);
+ return user_count * sizeof(uintptr_t);
// end OS(DARWIN)
#elif OS(WINDOWS)
regs.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
- GetThreadContext(platformThread, &regs);
+ GetThreadContext(platformThreadHandle, &regs);
return sizeof(CONTEXT);
#elif USE(PTHREADS)
- pthread_attr_init(&regs);
+ pthread_attr_init(&regs.attribute);
#if HAVE(PTHREAD_NP_H) || OS(NETBSD)
+#if !OS(OPENBSD)
// e.g. on FreeBSD 5.4, neundorf@kde.org
- pthread_attr_get_np(platformThread, &regs);
+ pthread_attr_get_np(platformThread, &regs.attribute);
+#endif
#else
// FIXME: this function is non-portable; other POSIX systems may have different np alternatives
- pthread_getattr_np(platformThread, &regs);
+ pthread_getattr_np(platformThread, &regs.attribute);
#endif
+ regs.machineContext = suspendedMachineContext;
return 0;
#else
#error Need a way to get thread registers on this platform
#endif
}
-static inline void* otherThreadStackPointer(const PlatformThreadRegisters& regs)
+void* MachineThreads::ThreadData::Registers::stackPointer() const
{
#if OS(DARWIN)
@@ -414,77 +560,534 @@ static inline void* otherThreadStackPointer(const PlatformThreadRegisters& regs)
#endif
#elif USE(PTHREADS)
+
+#if OS(FREEBSD) && ENABLE(JIT)
+
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_esp);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_rsp);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.__gregs[_REG_SP]);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_gpregs.gp_sp);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_regs[29]);
+#else
+#error Unknown Architecture
+#endif
+
+#elif defined(__GLIBC__) && ENABLE(JIT)
+
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_ESP]);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_RSP]);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.arm_sp);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.sp);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[29]);
+#else
+#error Unknown Architecture
+#endif
+
+#else
void* stackBase = 0;
size_t stackSize = 0;
- int rc = pthread_attr_getstack(&regs, &stackBase, &stackSize);
+#if OS(OPENBSD)
+ stack_t ss;
+ int rc = pthread_stackseg_np(pthread_self(), &ss);
+ stackBase = (void*)((size_t) ss.ss_sp - ss.ss_size);
+ stackSize = ss.ss_size;
+#else
+ int rc = pthread_attr_getstack(&regs.attribute, &stackBase, &stackSize);
+#endif
(void)rc; // FIXME: Deal with error code somehow? Seems fatal.
ASSERT(stackBase);
return static_cast<char*>(stackBase) + stackSize;
+#endif
+
#else
#error Need a way to get the stack pointer for another thread on this platform
#endif
}
-static void freePlatformThreadRegisters(PlatformThreadRegisters& regs)
+#if ENABLE(SAMPLING_PROFILER)
+void* MachineThreads::ThreadData::Registers::framePointer() const
{
+#if OS(DARWIN)
+
+#if __DARWIN_UNIX03
+
+#if CPU(X86)
+ return reinterpret_cast<void*>(regs.__ebp);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>(regs.__rbp);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>(regs.__r[11]);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>(regs.__x[29]);
+#else
+#error Unknown Architecture
+#endif
+
+#else // !__DARWIN_UNIX03
+
+#if CPU(X86)
+ return reinterpret_cast<void*>(regs.esp);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>(regs.rsp);
+#else
+#error Unknown Architecture
+#endif
+
+#endif // __DARWIN_UNIX03
+
+// end OS(DARWIN)
+#elif OS(WINDOWS)
+
+#if CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.R11);
+#elif CPU(MIPS)
+#error Dont know what to do with mips. Do we even need this?
+#elif CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.Ebp);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.Rbp);
+#else
+#error Unknown Architecture
+#endif
+
+#elif OS(FREEBSD)
+
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_ebp);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_rbp);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.__gregs[_REG_FP]);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_gpregs.gp_x[29]);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_regs[30]);
+#else
+#error Unknown Architecture
+#endif
+
+#elif defined(__GLIBC__)
+
+// The following sequence depends on glibc's sys/ucontext.h.
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_EBP]);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_RBP]);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.arm_fp);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.regs[29]);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[30]);
+#else
+#error Unknown Architecture
+#endif
+
+#else
+#error Need a way to get the frame pointer for another thread on this platform
+#endif
+}
+
+void* MachineThreads::ThreadData::Registers::instructionPointer() const
+{
+#if OS(DARWIN)
+
+#if __DARWIN_UNIX03
+
+#if CPU(X86)
+ return reinterpret_cast<void*>(regs.__eip);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>(regs.__rip);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>(regs.__pc);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>(regs.__pc);
+#else
+#error Unknown Architecture
+#endif
+
+#else // !__DARWIN_UNIX03
+#if CPU(X86)
+ return reinterpret_cast<void*>(regs.eip);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>(regs.rip);
+#else
+#error Unknown Architecture
+#endif
+
+#endif // __DARWIN_UNIX03
+
+// end OS(DARWIN)
+#elif OS(WINDOWS)
+
+#if CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.Pc);
+#elif CPU(MIPS)
+#error Dont know what to do with mips. Do we even need this?
+#elif CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.Eip);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.Rip);
+#else
+#error Unknown Architecture
+#endif
+
+#elif OS(FREEBSD)
+
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_eip);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_rip);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.__gregs[_REG_PC]);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_gpregs.gp_elr);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_pc);
+#else
+#error Unknown Architecture
+#endif
+
+#elif defined(__GLIBC__)
+
+// The following sequence depends on glibc's sys/ucontext.h.
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_EIP]);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_RIP]);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.arm_pc);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.pc);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.pc);
+#else
+#error Unknown Architecture
+#endif
+
+#else
+#error Need a way to get the instruction pointer for another thread on this platform
+#endif
+}
+
+void* MachineThreads::ThreadData::Registers::llintPC() const
+{
+ // LLInt uses regT4 as PC.
+#if OS(DARWIN)
+
+#if __DARWIN_UNIX03
+
+#if CPU(X86)
+ static_assert(LLInt::LLIntPC == X86Registers::esi, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.__esi);
+#elif CPU(X86_64)
+ static_assert(LLInt::LLIntPC == X86Registers::r8, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.__r8);
+#elif CPU(ARM)
+ static_assert(LLInt::LLIntPC == ARMRegisters::r8, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.__r[8]);
+#elif CPU(ARM64)
+ static_assert(LLInt::LLIntPC == ARM64Registers::x4, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.__x[4]);
+#else
+#error Unknown Architecture
+#endif
+
+#else // !__DARWIN_UNIX03
+#if CPU(X86)
+ static_assert(LLInt::LLIntPC == X86Registers::esi, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.esi);
+#elif CPU(X86_64)
+ static_assert(LLInt::LLIntPC == X86Registers::r8, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>(regs.r8);
+#else
+#error Unknown Architecture
+#endif
+
+#endif // __DARWIN_UNIX03
+
+// end OS(DARWIN)
+#elif OS(WINDOWS)
+
+#if CPU(ARM)
+ static_assert(LLInt::LLIntPC == ARMRegisters::r8, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>((uintptr_t) regs.R8);
+#elif CPU(MIPS)
+#error Dont know what to do with mips. Do we even need this?
+#elif CPU(X86)
+ static_assert(LLInt::LLIntPC == X86Registers::esi, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>((uintptr_t) regs.Esi);
+#elif CPU(X86_64)
+ static_assert(LLInt::LLIntPC == X86Registers::r10, "Wrong LLInt PC.");
+ return reinterpret_cast<void*>((uintptr_t) regs.R10);
+#else
+#error Unknown Architecture
+#endif
+
+#elif OS(FREEBSD)
+
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_esi);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_r8);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.__gregs[_REG_R8]);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_gpregs.gp_x[4]);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.mc_regs[12]);
+#else
+#error Unknown Architecture
+#endif
+
+#elif defined(__GLIBC__)
+
+// The following sequence depends on glibc's sys/ucontext.h.
+#if CPU(X86)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_ESI]);
+#elif CPU(X86_64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[REG_R8]);
+#elif CPU(ARM)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.arm_r8);
+#elif CPU(ARM64)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.regs[4]);
+#elif CPU(MIPS)
+ return reinterpret_cast<void*>((uintptr_t) regs.machineContext.gregs[12]);
+#else
+#error Unknown Architecture
+#endif
+
+#else
+#error Need a way to get the LLIntPC for another thread on this platform
+#endif
+}
+#endif // ENABLE(SAMPLING_PROFILER)
+
+void MachineThreads::ThreadData::freeRegisters(ThreadData::Registers& registers)
+{
+ ThreadData::Registers::PlatformRegisters& regs = registers.regs;
#if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN)
- pthread_attr_destroy(&regs);
+ pthread_attr_destroy(&regs.attribute);
#else
UNUSED_PARAM(regs);
#endif
}
-void MachineThreads::gatherFromOtherThread(ConservativeRoots& conservativeRoots, Thread* thread)
+static inline int osRedZoneAdjustment()
+{
+ int redZoneAdjustment = 0;
+#if !OS(WINDOWS)
+#if CPU(X86_64)
+ // See http://people.freebsd.org/~obrien/amd64-elf-abi.pdf Section 3.2.2.
+ redZoneAdjustment = -128;
+#elif CPU(ARM64)
+ // See https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html#//apple_ref/doc/uid/TP40013702-SW7
+ redZoneAdjustment = -128;
+#endif
+#endif // !OS(WINDOWS)
+ return redZoneAdjustment;
+}
+
+std::pair<void*, size_t> MachineThreads::ThreadData::captureStack(void* stackTop)
{
- PlatformThreadRegisters regs;
- size_t regSize = getPlatformThreadRegisters(thread->platformThread, regs);
+ char* begin = reinterpret_cast_ptr<char*>(stackBase);
+ char* end = bitwise_cast<char*>(WTF::roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(stackTop)));
+ ASSERT(begin >= end);
- conservativeRoots.add(static_cast<void*>(&regs), static_cast<void*>(reinterpret_cast<char*>(&regs) + regSize));
+ char* endWithRedZone = end + osRedZoneAdjustment();
+ ASSERT(WTF::roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(endWithRedZone)) == reinterpret_cast<uintptr_t>(endWithRedZone));
- void* stackPointer = otherThreadStackPointer(regs);
- void* stackBase = thread->stackBase;
- swapIfBackwards(stackPointer, stackBase);
- conservativeRoots.add(stackPointer, stackBase);
+ if (endWithRedZone < stackEnd)
+ endWithRedZone = reinterpret_cast_ptr<char*>(stackEnd);
- freePlatformThreadRegisters(regs);
+ std::swap(begin, endWithRedZone);
+ return std::make_pair(begin, endWithRedZone - begin);
}
-void MachineThreads::gatherConservativeRoots(ConservativeRoots& conservativeRoots, void* stackCurrent)
+SUPPRESS_ASAN
+static void copyMemory(void* dst, const void* src, size_t size)
+{
+ size_t dstAsSize = reinterpret_cast<size_t>(dst);
+ size_t srcAsSize = reinterpret_cast<size_t>(src);
+ RELEASE_ASSERT(dstAsSize == WTF::roundUpToMultipleOf<sizeof(intptr_t)>(dstAsSize));
+ RELEASE_ASSERT(srcAsSize == WTF::roundUpToMultipleOf<sizeof(intptr_t)>(srcAsSize));
+ RELEASE_ASSERT(size == WTF::roundUpToMultipleOf<sizeof(intptr_t)>(size));
+
+ intptr_t* dstPtr = reinterpret_cast<intptr_t*>(dst);
+ const intptr_t* srcPtr = reinterpret_cast<const intptr_t*>(src);
+ size /= sizeof(intptr_t);
+ while (size--)
+ *dstPtr++ = *srcPtr++;
+}
+
+
+
+// This function must not call malloc(), free(), or any other function that might
+// acquire a lock. Since 'thread' is suspended, trying to acquire a lock
+// will deadlock if 'thread' holds that lock.
+// This function, specifically the memory copying, was causing problems with Address Sanitizer in
+// apps. Since we cannot blacklist the system memcpy we must use our own naive implementation,
+// copyMemory, for ASan to work on either instrumented or non-instrumented builds. This is not a
+// significant performance loss as tryCopyOtherThreadStack is only called as part of an O(heapsize)
+// operation. As the heap is generally much larger than the stack the performance hit is minimal.
+// See: https://bugs.webkit.org/show_bug.cgi?id=146297
+void MachineThreads::tryCopyOtherThreadStack(Thread* thread, void* buffer, size_t capacity, size_t* size)
{
- gatherFromCurrentThread(conservativeRoots, stackCurrent);
+ Thread::Registers registers;
+ size_t registersSize = thread->getRegisters(registers);
+
+ // This is a workaround for <rdar://problem/27607384>. During thread initialization,
+ // for some target platforms, thread state is momentarily set to 0 before being
+ // filled in with the target thread's real register values. As a result, there's
+ // a race condition that may result in us getting a null stackPointer.
+ // This issue may manifest with workqueue threads where the OS may choose to recycle
+ // a thread for an expired task.
+ //
+ // The workaround is simply to indicate that there's nothing to copy and return.
+ // This is correct because we will only ever observe a null pointer during thread
+ // initialization. Hence, by definition, there's nothing there that we need to scan
+ // yet, and therefore, nothing that needs to be copied.
+ if (UNLIKELY(!registers.stackPointer())) {
+ *size = 0;
+ return;
+ }
- if (m_threadSpecific) {
- PlatformThread currentPlatformThread = getCurrentPlatformThread();
+ std::pair<void*, size_t> stack = thread->captureStack(registers.stackPointer());
- MutexLocker lock(m_registeredThreadsMutex);
+ bool canCopy = *size + registersSize + stack.second <= capacity;
-#ifndef NDEBUG
- // Forbid malloc during the gather phase. The gather phase suspends
- // threads, so a malloc during gather would risk a deadlock with a
- // thread that had been suspended while holding the malloc lock.
- fastMallocForbid();
-#endif
- for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
- if (!equalThread(thread->platformThread, currentPlatformThread))
- suspendThread(thread->platformThread);
- }
+ if (canCopy)
+ copyMemory(static_cast<char*>(buffer) + *size, &registers, registersSize);
+ *size += registersSize;
- // It is safe to access the registeredThreads list, because we earlier asserted that locks are being held,
- // and since this is a shared heap, they are real locks.
- for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
- if (!equalThread(thread->platformThread, currentPlatformThread))
- gatherFromOtherThread(conservativeRoots, thread);
- }
+ if (canCopy)
+ copyMemory(static_cast<char*>(buffer) + *size, stack.first, stack.second);
+ *size += stack.second;
- for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
- if (!equalThread(thread->platformThread, currentPlatformThread))
- resumeThread(thread->platformThread);
- }
+ thread->freeRegisters(registers);
+}
-#ifndef NDEBUG
- fastMallocAllow();
+bool MachineThreads::tryCopyOtherThreadStacks(LockHolder&, void* buffer, size_t capacity, size_t* size)
+{
+ // Prevent two VMs from suspending each other's threads at the same time,
+ // which can cause deadlock: <rdar://problem/20300842>.
+ static StaticLock mutex;
+ std::lock_guard<StaticLock> lock(mutex);
+
+ *size = 0;
+
+ PlatformThread currentPlatformThread = getCurrentPlatformThread();
+ int numberOfThreads = 0; // Using 0 to denote that we haven't counted the number of threads yet.
+ int index = 1;
+ Thread* threadsToBeDeleted = nullptr;
+
+ Thread* previousThread = nullptr;
+ for (Thread* thread = m_registeredThreads; thread; index++) {
+ if (*thread != currentPlatformThread) {
+ bool success = thread->suspend();
+#if OS(DARWIN)
+ if (!success) {
+ if (!numberOfThreads) {
+ for (Thread* countedThread = m_registeredThreads; countedThread; countedThread = countedThread->next)
+ numberOfThreads++;
+ }
+
+ // Re-do the suspension to get the actual failure result for logging.
+ kern_return_t error = thread_suspend(thread->platformThread());
+ ASSERT(error != KERN_SUCCESS);
+
+ WTFReportError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION,
+ "JavaScript garbage collection encountered an invalid thread (err 0x%x): Thread [%d/%d: %p] platformThread %p.",
+ error, index, numberOfThreads, thread, reinterpret_cast<void*>(thread->platformThread()));
+
+ // Put the invalid thread on the threadsToBeDeleted list.
+ // We can't just delete it here because we have suspended other
+ // threads, and they may still be holding the C heap lock which
+ // we need for deleting the invalid thread. Hence, we need to
+ // defer the deletion till after we have resumed all threads.
+ Thread* nextThread = thread->next;
+ thread->next = threadsToBeDeleted;
+ threadsToBeDeleted = thread;
+
+ if (previousThread)
+ previousThread->next = nextThread;
+ else
+ m_registeredThreads = nextThread;
+ thread = nextThread;
+ continue;
+ }
+#else
+ UNUSED_PARAM(numberOfThreads);
+ UNUSED_PARAM(previousThread);
+ ASSERT_UNUSED(success, success);
#endif
+ }
+ previousThread = thread;
+ thread = thread->next;
+ }
+
+ for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
+ if (*thread != currentPlatformThread)
+ tryCopyOtherThreadStack(thread, buffer, capacity, size);
+ }
+
+ for (Thread* thread = m_registeredThreads; thread; thread = thread->next) {
+ if (*thread != currentPlatformThread)
+ thread->resume();
}
+
+ for (Thread* thread = threadsToBeDeleted; thread; ) {
+ Thread* nextThread = thread->next;
+ delete thread;
+ thread = nextThread;
+ }
+
+ return *size <= capacity;
+}
+
+static void growBuffer(size_t size, void** buffer, size_t* capacity)
+{
+ if (*buffer)
+ fastFree(*buffer);
+
+ *capacity = WTF::roundUpToMultipleOf(WTF::pageSize(), size * 2);
+ *buffer = fastMalloc(*capacity);
+}
+
+void MachineThreads::gatherConservativeRoots(ConservativeRoots& conservativeRoots, JITStubRoutineSet& jitStubRoutines, CodeBlockSet& codeBlocks, CurrentThreadState* currentThreadState)
+{
+ if (currentThreadState)
+ gatherFromCurrentThread(conservativeRoots, jitStubRoutines, codeBlocks, *currentThreadState);
+
+ size_t size;
+ size_t capacity = 0;
+ void* buffer = nullptr;
+ LockHolder lock(m_registeredThreadsMutex);
+ while (!tryCopyOtherThreadStacks(lock, buffer, capacity, &size))
+ growBuffer(size, &buffer, &capacity);
+
+ if (!buffer)
+ return;
+
+ conservativeRoots.add(buffer, static_cast<char*>(buffer) + size, jitStubRoutines, codeBlocks);
+ fastFree(buffer);
+}
+
+NEVER_INLINE int callWithCurrentThreadState(const ScopedLambda<void(CurrentThreadState&)>& lambda)
+{
+ DECLARE_AND_COMPUTE_CURRENT_THREAD_STATE(state);
+ lambda(state);
+ return 42; // Suppress tail call optimization.
}
} // namespace JSC