diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/heap/MachineStackMarker.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/heap/MachineStackMarker.cpp')
-rw-r--r-- | Source/JavaScriptCore/heap/MachineStackMarker.cpp | 993 |
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 = ®isters; - void* registersEnd = reinterpret_cast<void*>(roundUpToMultipleOf<sizeof(void*)>(reinterpret_cast<uintptr_t>(®isters + 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, ®s); + GetThreadContext(platformThreadHandle, ®s); return sizeof(CONTEXT); #elif USE(PTHREADS) - pthread_attr_init(®s); + pthread_attr_init(®s.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, ®s); + pthread_attr_get_np(platformThread, ®s.attribute); +#endif #else // FIXME: this function is non-portable; other POSIX systems may have different np alternatives - pthread_getattr_np(platformThread, ®s); + pthread_getattr_np(platformThread, ®s.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(®s, &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(®s.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(®s); + pthread_attr_destroy(®s.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*>(®s), static_cast<void*>(reinterpret_cast<char*>(®s) + 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, ®isters, 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 |