| //===---------- ctthread.cc - Thread implementation for VMKit -------------===// |
| // |
| // The VMKit project |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MvmGC.h" |
| #include "mvm/VirtualMachine.h" |
| #include "mvm/Threads/Cond.h" |
| #include "mvm/Threads/Locks.h" |
| #include "mvm/Threads/Thread.h" |
| |
| #include <cassert> |
| #include <signal.h> |
| #include <cstdio> |
| #include <ctime> |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sys/mman.h> |
| #include <sched.h> |
| #include <unistd.h> |
| |
| using namespace mvm; |
| |
| int Thread::kill(void* tid, int signo) { |
| return pthread_kill((pthread_t)tid, signo); |
| } |
| |
| int Thread::kill(int signo) { |
| return pthread_kill((pthread_t)internalThreadID, signo); |
| } |
| |
| void Thread::exit(int value) { |
| pthread_exit((void*)value); |
| } |
| |
| void Thread::yield(void) { |
| Thread* th = mvm::Thread::get(); |
| if (th->isMvmThread()) { |
| if (th->doYield && !th->inRV) th->joinRV(); |
| } |
| sched_yield(); |
| } |
| |
| void Thread::joinRV() { |
| MyVM->rendezvous.join(); |
| } |
| |
| void Thread::startKnownFrame(KnownFrame& F) { |
| // Get the caller of this function |
| void** cur = (void**)FRAME_PTR(); |
| // Get the caller of the caller. |
| cur = (void**)cur[0]; |
| F.previousFrame = lastKnownFrame; |
| F.currentFP = cur; |
| lastKnownFrame = &F; |
| } |
| |
| void Thread::endKnownFrame() { |
| lastKnownFrame = lastKnownFrame->previousFrame; |
| } |
| |
| #if defined(__MACH__) |
| #define SELF_HANDLE RTLD_DEFAULT |
| #else |
| #define SELF_HANDLE 0 |
| #endif |
| |
| void Thread::internalThrowException() { |
| #ifdef RUNTIME_DWARF_EXCEPTIONS |
| // Use dlsym instead of getting the functions statically with extern "C" |
| // because gcc compiles exceptions differently. |
| typedef void* (*cxa_allocate_exception_type)(unsigned); |
| typedef void (*cxa_throw_type)(void*, void*, void*); |
| |
| static cxa_allocate_exception_type cxa_allocate_exception = |
| (cxa_allocate_exception_type)(uintptr_t) |
| dlsym(SELF_HANDLE, "__cxa_allocate_exception"); |
| |
| static cxa_throw_type cxa_throw = |
| (cxa_throw_type)(uintptr_t) |
| dlsym(SELF_HANDLE, "__cxa_throw"); |
| |
| void* exc = cxa_allocate_exception(0); |
| // 32 = sizeof(_Unwind_Exception) in libgcc... |
| internalPendingException = (void*)((uintptr_t)exc - 32); |
| cxa_throw(exc, 0, 0); |
| #else |
| #if defined(__MACH__) |
| _longjmp(lastExceptionBuffer->buffer, 1); |
| #else |
| longjmp(lastExceptionBuffer->buffer, 1); |
| #endif |
| #endif |
| } |
| |
| void Thread::printBacktrace() { |
| StackWalker Walker(this); |
| |
| while (MethodInfo* MI = Walker.get()) { |
| MI->print(Walker.ip, Walker.addr); |
| ++Walker; |
| } |
| } |
| |
| void Thread::getFrameContext(void** buffer) { |
| mvm::StackWalker Walker(this); |
| uint32_t i = 0; |
| |
| while (void* ip = *Walker) { |
| buffer[i++] = ip; |
| ++Walker; |
| } |
| } |
| |
| uint32_t Thread::getFrameContextLength() { |
| mvm::StackWalker Walker(this); |
| uint32_t i = 0; |
| |
| while (*Walker) { |
| ++i; |
| ++Walker; |
| } |
| return i; |
| } |
| |
| MethodInfo* StackWalker::get() { |
| if (addr == thread->baseSP) return 0; |
| ip = FRAME_IP(addr); |
| bool isStub = ((unsigned char*)ip)[0] == 0xCE; |
| if (isStub) ip = addr[2]; |
| return thread->MyVM->IPToMethodInfo(ip); |
| } |
| |
| void* StackWalker::operator*() { |
| if (addr == thread->baseSP) return 0; |
| ip = FRAME_IP(addr); |
| bool isStub = ((unsigned char*)ip)[0] == 0xCE; |
| if (isStub) ip = addr[2]; |
| return ip; |
| } |
| |
| void StackWalker::operator++() { |
| if (addr < thread->baseSP && addr < addr[0]) { |
| if (frame && addr == frame->currentFP) { |
| frame = frame->previousFrame; |
| if (frame) { |
| addr = (void**)frame->currentFP; |
| frame = frame->previousFrame; |
| } else { |
| addr = (void**)addr[0]; |
| } |
| } else { |
| addr = (void**)addr[0]; |
| } |
| } else { |
| addr = (void**)thread->baseSP; |
| } |
| } |
| |
| |
| uintptr_t Thread::baseAddr = 0; |
| |
| // These could be set at runtime. |
| #define STACK_SIZE 0x100000 |
| #define NR_THREADS 255 |
| |
| #if (__WORDSIZE == 64) |
| #define START_ADDR 0x110000000 |
| #define END_ADDR 0x170000000 |
| #else |
| #define START_ADDR 0x10000000 |
| #define END_ADDR 0x70000000 |
| #endif |
| |
| /// StackThreadManager - This class allocates all stacks for threads. Because |
| /// we want fast access to thread local data, and can not rely on platform |
| /// dependent thread local storage (eg pthread keys are inefficient, tls is |
| /// specific to Linux), we put thread local data at the bottom of the |
| /// stack. A simple mask computes the thread local data , based on the current |
| /// stack pointer. |
| // |
| /// The stacks are allocated at boot time. They must all be in the memory range |
| /// 0x?0000000 and Ox(?+1)0000000, so that the thread local data can be computed |
| /// and threads have a unique ID. |
| /// |
| class StackThreadManager { |
| public: |
| uintptr_t baseAddr; |
| uint32 allocPtr; |
| uint32 used[NR_THREADS]; |
| LockNormal stackLock; |
| |
| StackThreadManager() { |
| baseAddr = 0; |
| uintptr_t ptr = START_ADDR; |
| |
| // Do an mmap at a fixed address. If the mmap fails for a given address |
| // use the next one. |
| while (!baseAddr && ptr != END_ADDR) { |
| ptr = ptr + 0x10000000; |
| #if defined (__MACH__) |
| uint32 flags = MAP_PRIVATE | MAP_ANON | MAP_FIXED; |
| #else |
| uint32 flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; |
| #endif |
| baseAddr = (uintptr_t)mmap((void*)ptr, STACK_SIZE * NR_THREADS, |
| PROT_READ | PROT_WRITE, flags, -1, 0); |
| if (baseAddr == (uintptr_t)MAP_FAILED) baseAddr = 0; |
| } |
| if (!baseAddr) { |
| fprintf(stderr, "Can not allocate thread memory\n"); |
| abort(); |
| } |
| |
| // Protect the page after the first page. The first page contains thread |
| // specific data. The second page has no access rights to catch stack |
| // overflows. |
| uint32 pagesize = getpagesize(); |
| for (uint32 i = 0; i < NR_THREADS; ++i) { |
| uintptr_t addr = baseAddr + (i * STACK_SIZE) + pagesize; |
| mprotect((void*)addr, pagesize, PROT_NONE); |
| } |
| |
| memset((void*)used, 0, NR_THREADS * sizeof(uint32)); |
| allocPtr = 0; |
| mvm::Thread::baseAddr = baseAddr; |
| } |
| |
| uintptr_t allocate() { |
| stackLock.lock(); |
| uint32 myIndex = 0; |
| do { |
| if (!used[myIndex]) { |
| used[myIndex] = 1; |
| break; |
| } |
| ++myIndex; |
| } while (myIndex != NR_THREADS); |
| |
| stackLock.unlock(); |
| |
| if (myIndex != NR_THREADS) |
| return baseAddr + myIndex * STACK_SIZE; |
| |
| return 0; |
| } |
| |
| }; |
| |
| |
| /// Static allocate a stack manager. In the future, this should be virtual |
| /// machine specific. |
| StackThreadManager TheStackManager; |
| |
| extern void sigsegvHandler(int, siginfo_t*, void*); |
| |
| |
| #if defined(__MACH__) |
| # define SIGGC SIGXCPU |
| #else |
| # define SIGGC SIGPWR |
| #endif |
| |
| static void siggcHandler(int) { |
| mvm::Thread* th = mvm::Thread::get(); |
| th->MyVM->rendezvous.join(); |
| } |
| |
| /// internalThreadStart - The initial function called by a thread. Sets some |
| /// thread specific data, registers the thread to the GC and calls the |
| /// given routine of th. |
| /// |
| void Thread::internalThreadStart(mvm::Thread* th) { |
| th->baseSP = (void*)&th; |
| |
| // Set an alternate stack for handling stack overflows from VM code. |
| stack_t sigsegvStack; |
| sigsegvStack.ss_size = getpagesize(); |
| sigsegvStack.ss_flags = 0; |
| sigsegvStack.ss_sp = (void*)th; |
| sigaltstack(&sigsegvStack, NULL); |
| |
| // Set the SIGSEGV handler to diagnose errors. |
| struct sigaction sa; |
| sigset_t mask; |
| sigfillset(&mask); |
| sa.sa_flags = SA_ONSTACK | SA_SIGINFO; |
| sa.sa_mask = mask; |
| sa.sa_sigaction = sigsegvHandler; |
| sigaction(SIGSEGV, &sa, NULL); |
| |
| // Set the SIGGC handler for uncooperative rendezvous. |
| sigaction(SIGGC, 0, &sa); |
| sigfillset(&mask); |
| sa.sa_mask = mask; |
| sa.sa_handler = siggcHandler; |
| sa.sa_flags |= SA_RESTART; |
| sigaction(SIGGC, &sa, NULL); |
| |
| assert(th->MyVM && "VM not set in a thread"); |
| #ifdef ISOLATE |
| th->IsolateID = th->MyVM->IsolateID; |
| #endif |
| th->MyVM->addThread(th); |
| th->routine(th); |
| th->MyVM->removeThread(th); |
| |
| } |
| |
| |
| |
| /// start - Called by the creator of the thread to run the new thread. |
| /// The thread is in a detached state, because each virtual machine has |
| /// its own way of waiting for created threads. |
| int Thread::start(void (*fct)(mvm::Thread*)) { |
| pthread_attr_t attributs; |
| pthread_attr_init(&attributs); |
| pthread_attr_setstack(&attributs, this, STACK_SIZE); |
| routine = fct; |
| int res = pthread_create((pthread_t*)(void*)(&internalThreadID), &attributs, |
| (void* (*)(void *))internalThreadStart, this); |
| pthread_detach((pthread_t)internalThreadID); |
| pthread_attr_destroy(&attributs); |
| return res; |
| } |
| |
| |
| /// operator new - Get a stack from the stack manager. The Thread object |
| /// will be placed in the first page at the bottom of the stack. Hence |
| /// Thread objects can not exceed a page. |
| void* Thread::operator new(size_t sz) { |
| assert(sz < (size_t)getpagesize() && "Thread local data too big"); |
| void* res = (void*)TheStackManager.allocate(); |
| // Give it a second chance. |
| if (!res) { |
| Collector::collect(); |
| res = (void*)TheStackManager.allocate(); |
| } |
| return res; |
| } |
| |
| /// operator delete - Remove the stack of the thread from the list of stacks |
| /// in use. |
| void Thread::operator delete(void* th) { |
| Thread* Th = (Thread*)th; |
| uintptr_t index = ((uintptr_t)Th->baseSP & Thread::IDMask); |
| index = (index & ~TheStackManager.baseAddr) >> 20; |
| TheStackManager.used[index] = 0; |
| } |
| |
| void Thread::killForRendezvous() { |
| int res = kill(SIGGC); |
| assert(!res && "Error on kill"); |
| } |