blob: 7d79f0e74aaeebf1d1aa11a26341ed2913318079 [file] [log] [blame]
//===---------- 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 "debug.h"
#include "mvm/GC.h"
#include "mvm/MethodInfo.h"
#include "mvm/VirtualMachine.h"
#include "mvm/Threads/Cond.h"
#include "mvm/Threads/Locks.h"
#include "mvm/Threads/Thread.h"
#include "mvm/VMKit.h"
#include <cassert>
#include <csetjmp>
#include <cstdio>
#include <ctime>
#include <dlfcn.h>
#include <errno.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
using namespace mvm;
Thread::Thread(VMKit* vmk) {
#ifdef RUNTIME_DWARF_EXCEPTIONS
internalPendingException = 0;
#else
lastExceptionBuffer = 0;
#endif
vmkit = vmk;
lastKnownFrame = 0;
pendingException = 0;
allVmsData = 0;
state = 0; // not daemon, not running
vmk->registerPreparedThread(this);
}
void Thread::setDaemon() {
if((state & THREAD_RUNNING) && !(state & THREAD_DAEMON))
vmkit->nonDaemonThreadsManager.leaveNonDaemonMode();
state |= THREAD_DAEMON;
}
void Thread::setNonDaemon() {
if((state & THREAD_RUNNING) && (state & THREAD_DAEMON))
vmkit->nonDaemonThreadsManager.enterNonDaemonMode();
state &= ~THREAD_DAEMON;
}
void Thread::attach(VirtualMachine* vm) {
vmData = allVmsData[vm->vmID];
if(!vmData) {
vmkit->vmkitLock();
vmData = allVmsData[vm->vmID] = vm->buildVMThreadData(this);
vmkit->vmkitUnlock();
}
}
// must be protected by rendezvous.threadLock
void Thread::reallocAllVmsData(int old, int n) {
VMThreadData **newData = new VMThreadData*[n];
if(old) {
memcpy(newData, allVmsData, old*sizeof(VMThreadData*));
VMThreadData **oldData = allVmsData;
allVmsData = newData;
delete oldData;
} else
allVmsData = newData;
memset(allVmsData + old*sizeof(VMThreadData*), 0, (n-old)*sizeof(VMThreadData*));
}
void Thread::tracer(uintptr_t closure) {
mvm::Collector::markAndTraceRoot(&pendingException, closure);
// should we take the vmkit lock? I suppose that all the threads are suspended during the collection...
for(size_t i=0; i<vmkit->vmsArraySize; i++)
if(allVmsData[i]) {
allVmsData[i]->tracer(closure);
}
}
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->vmkit->rendezvous.join();
}
}
sched_yield();
}
void Thread::joinRVBeforeEnter() {
vmkit->rendezvous.joinBeforeUncooperative();
}
void Thread::joinRVAfterLeave(void* savedSP) {
vmkit->rendezvous.joinAfterUncooperative(savedSP);
}
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;
// This is used as a marker.
F.currentIP = NULL;
lastKnownFrame = &F;
}
void Thread::endKnownFrame() {
assert(lastKnownFrame->currentIP == NULL);
lastKnownFrame = lastKnownFrame->previousFrame;
}
void Thread::startUnknownFrame(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;
F.currentIP = FRAME_IP(cur);
lastKnownFrame = &F;
}
void Thread::endUnknownFrame() {
assert(lastKnownFrame->currentIP != NULL);
lastKnownFrame = lastKnownFrame->previousFrame;
}
#if defined(__MACH__)
#define SELF_HANDLE RTLD_DEFAULT
#else
#define SELF_HANDLE 0
#endif
Thread* Thread::setPendingException(gc *obj) {
llvm_gcroot(obj, 0);
assert(pendingException == 0 && "pending exception already there?");
pendingException = obj;
return this;
}
void Thread::throwIt() {
assert(pendingException);
#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->vmkit->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) {
assert((addr < thread->baseSP) && "Corrupted stack");
assert((addr < addr[0]) && "Corrupted stack");
if ((frame != NULL) && (addr == frame->currentFP)) {
assert(frame->currentIP == NULL);
frame = frame->previousFrame;
assert(frame != NULL);
assert(frame->currentIP != NULL);
addr = (void**)frame->currentFP;
frame = frame->previousFrame;
} else {
addr = (void**)addr[0];
}
}
}
StackWalker::StackWalker(mvm::Thread* th) {
thread = th;
frame = th->lastKnownFrame;
if (mvm::Thread::get() == th) {
addr = (void**)FRAME_PTR();
addr = (void**)addr[0];
} else {
addr = (void**)th->waitOnSP();
if (frame) {
assert(frame->currentFP >= addr);
}
if (frame && (addr == frame->currentFP)) {
frame = frame->previousFrame;
assert((frame == NULL) || (frame->currentIP == NULL));
}
}
assert(addr && "No address to start with");
}
#ifdef WITH_LLVM_GCC
void Thread::scanStack(uintptr_t closure) {
StackWalker Walker(this);
while (MethodInfo* MI = Walker.get()) {
MI->scan(closure, Walker.ip, Walker.addr);
++Walker;
}
}
#else
void Thread::scanStack(uintptr_t closure) {
register unsigned int **max = (unsigned int**)(void*)this->baseSP;
if (mvm::Thread::get() != this) {
register unsigned int **cur = (unsigned int**)this->waitOnSP();
for(; cur<max; cur++) Collector::scanObject((void**)cur, closure);
} else {
jmp_buf buf;
setjmp(buf);
register unsigned int **cur = (unsigned int**)&buf;
for(; cur<max; cur++) Collector::scanObject((void**)cur, closure);
}
}
#endif
void Thread::enterUncooperativeCode(unsigned level) {
if (isMvmThread()) {
if (!inRV) {
assert(!lastSP && "SP already set when entering uncooperative code");
// Get the caller.
void* temp = FRAME_PTR();
// Make sure to at least get the caller of the caller.
++level;
while (level--) temp = ((void**)temp)[0];
// The cas is not necessary, but it does a memory barrier.
__sync_bool_compare_and_swap(&lastSP, 0, temp);
if (doYield) joinRVBeforeEnter();
assert(lastSP && "No last SP when entering uncooperative code");
}
}
}
void Thread::enterUncooperativeCode(void* SP) {
if (isMvmThread()) {
if (!inRV) {
assert(!lastSP && "SP already set when entering uncooperative code");
// The cas is not necessary, but it does a memory barrier.
__sync_bool_compare_and_swap(&lastSP, 0, SP);
if (doYield) joinRVBeforeEnter();
assert(lastSP && "No last SP when entering uncooperative code");
}
}
}
void Thread::leaveUncooperativeCode() {
if (isMvmThread()) {
if (!inRV) {
assert(lastSP && "No last SP when leaving uncooperative code");
void* savedSP = lastSP;
// The cas is not necessary, but it does a memory barrier.
__sync_bool_compare_and_swap(&lastSP, lastSP, 0);
// A rendezvous has just been initiated, join it.
if (doYield) joinRVAfterLeave(savedSP);
assert(!lastSP && "SP has a value after leaving uncooperative code");
}
}
}
void* Thread::waitOnSP() {
// First see if we can get lastSP directly.
void* sp = lastSP;
if (sp) return sp;
// Then loop a fixed number of iterations to get lastSP.
for (uint32 count = 0; count < 1000; ++count) {
sp = lastSP;
if (sp) return sp;
}
// Finally, yield until lastSP is not set.
while ((sp = lastSP) == NULL) mvm::Thread::yield();
assert(sp != NULL && "Still no sp");
return sp;
}
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*);
/// 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 = FRAME_PTR();
// Set the SIGSEGV handler to diagnose errors.
struct sigaction sa;
sigset_t mask;
sigfillset(&mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_mask = mask;
sa.sa_sigaction = sigsegvHandler;
sigaction(SIGSEGV, &sa, NULL);
assert(th->vmkit && "VM not set in a thread");
th->vmkit->rendezvous.prepareForJoin();
th->routine(th);
th->state &= ~THREAD_RUNNING;
if(!(th->state & THREAD_DAEMON))
th->vmkit->nonDaemonThreadsManager.leaveNonDaemonMode();
th->vmkit->unregisterRunningThread(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;
// Make sure to add it in the list of threads before leaving this function:
// the garbage collector wants to trace this thread.
state |= THREAD_RUNNING;
if(!(state & THREAD_DAEMON))
vmkit->nonDaemonThreadsManager.enterNonDaemonMode();
vmkit->registerRunningThread(this);
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 == NULL) {
Collector::collect();
// Wait for the finalizer to have cleaned up the threads.
while (res == NULL) {
mvm::Thread::yield();
res = (void*)TheStackManager.allocate();
}
}
// Make sure the thread information is cleared.
memset(res, 0, sz);
return res;
}
void Thread::operator delete(void* th) {
uintptr_t index = ((uintptr_t)th & Thread::IDMask);
index = (index & ~TheStackManager.baseAddr) >> 20;
TheStackManager.used[index] = 0;
}
Thread::~Thread() {
// It seems like the pthread implementation in Linux is clearing with NULL
// the stack of the thread. So we have to get the thread id before
// calling pthread_join.
void* thread_id = internalThreadID;
if (thread_id != NULL) {
// Wait for the thread to die.
pthread_join((pthread_t)thread_id, NULL);
}
vmkit->unregisterPreparedThread(this);
}