#include #include #include #ifdef HX_WINDOWS #include #endif #ifndef HX_WINDOWS #include #endif DECLARE_TLS_DATA(class hxThreadInfo, tlsCurrentThread); // g_threadInfoMutex allows atomic access to g_nextThreadNumber static HxMutex g_threadInfoMutex; // Thread number 0 is reserved for the main thread static int g_nextThreadNumber = 1; // How to manage hxThreadInfo references for non haxe threads (main, extenal)? // HXCPP_THREAD_INFO_PTHREAD - use pthread api // HXCPP_THREAD_INFO_LOCAL - use thread_local storage // HXCPP_THREAD_INFO_SINGLETON - use one structure for all threads. Not ideal. #if __cplusplus > 199711L && !defined(__BORLANDC__) #define HXCPP_THREAD_INFO_LOCAL #elif defined (HXCPP_PTHREADS) #define HXCPP_THREAD_INFO_PTHREAD #else #define HXCPP_THREAD_INFO_SINGLETON #endif // --- Deque ---------------------------------------------------------- struct Deque : public Array_obj { Deque() : Array_obj(0,0) { } static Deque *Create() { Deque *result = new Deque(); result->mFinalizer = new hx::InternalFinalizer(result,clean); return result; } static void clean(hx::Object *inObj) { Deque *d = dynamic_cast(inObj); if (d) d->Clean(); } void Clean() { #ifdef HX_WINDOWS mMutex.Clean(); #endif mSemaphore.Clean(); } #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { Array_obj::__Visit(__inCtx); mFinalizer->Visit(__inCtx); } #endif #ifndef HX_THREAD_SEMAPHORE_LOCKABLE HxMutex mMutex; void PushBack(Dynamic inValue) { hx::EnterGCFreeZone(); AutoLock lock(mMutex); hx::ExitGCFreeZone(); push(inValue); mSemaphore.Set(); } void PushFront(Dynamic inValue) { hx::EnterGCFreeZone(); AutoLock lock(mMutex); hx::ExitGCFreeZone(); unshift(inValue); mSemaphore.Set(); } Dynamic PopFront(bool inBlock) { hx::EnterGCFreeZone(); AutoLock lock(mMutex); if (!inBlock) { hx::ExitGCFreeZone(); return shift(); } // Ok - wait for something on stack... while(!length) { mSemaphore.Reset(); lock.Unlock(); mSemaphore.Wait(); lock.Lock(); } hx::ExitGCFreeZone(); if (length==1) mSemaphore.Reset(); return shift(); } #else void PushBack(Dynamic inValue) { hx::EnterGCFreeZone(); AutoLock lock(mSemaphore); hx::ExitGCFreeZone(); push(inValue); mSemaphore.QSet(); } void PushFront(Dynamic inValue) { hx::EnterGCFreeZone(); AutoLock lock(mSemaphore); hx::ExitGCFreeZone(); unshift(inValue); mSemaphore.QSet(); } Dynamic PopFront(bool inBlock) { hx::EnterGCFreeZone(); AutoLock lock(mSemaphore); while(inBlock && !length) mSemaphore.QWait(); hx::ExitGCFreeZone(); Dynamic result = shift(); if (length) mSemaphore.QSet(); return result; } #endif hx::InternalFinalizer *mFinalizer; HxSemaphore mSemaphore; }; Dynamic __hxcpp_deque_create() { return Deque::Create(); } void __hxcpp_deque_add(Dynamic q,Dynamic inVal) { Deque *d = dynamic_cast(q.mPtr); if (!d) throw HX_INVALID_OBJECT; d->PushBack(inVal); } void __hxcpp_deque_push(Dynamic q,Dynamic inVal) { Deque *d = dynamic_cast(q.mPtr); if (!d) throw HX_INVALID_OBJECT; d->PushFront(inVal); } Dynamic __hxcpp_deque_pop(Dynamic q,bool block) { Deque *d = dynamic_cast(q.mPtr); if (!d) throw HX_INVALID_OBJECT; return d->PopFront(block); } // --- Thread ---------------------------------------------------------- class hxThreadInfo : public hx::Object { public: HX_IS_INSTANCE_OF enum { _hx_ClassId = hx::clsIdThreadInfo }; hxThreadInfo(Dynamic inFunction, int inThreadNumber) : mFunction(inFunction), mThreadNumber(inThreadNumber), mTLS(0,0) { mSemaphore = new HxSemaphore; mDeque = Deque::Create(); HX_OBJ_WB_NEW_MARKED_OBJECT(this); } hxThreadInfo() { mSemaphore = 0; mDeque = Deque::Create(); HX_OBJ_WB_NEW_MARKED_OBJECT(this); } int GetThreadNumber() const { return mThreadNumber; } void CleanSemaphore() { delete mSemaphore; mSemaphore = 0; } void Send(Dynamic inMessage) { mDeque->PushBack(inMessage); } Dynamic ReadMessage(bool inBlocked) { return mDeque->PopFront(inBlocked); } String toString() { return String(GetThreadNumber()); } void SetTLS(int inID,Dynamic inVal) { mTLS->__SetItem(inID,inVal); } Dynamic GetTLS(int inID) { return mTLS[inID]; } void __Mark(hx::MarkContext *__inCtx) { HX_MARK_MEMBER(mFunction); HX_MARK_MEMBER(mTLS); if (mDeque) HX_MARK_OBJECT(mDeque); } #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { HX_VISIT_MEMBER(mFunction); HX_VISIT_MEMBER(mTLS); if (mDeque) HX_VISIT_OBJECT(mDeque); } #endif Array mTLS; HxSemaphore *mSemaphore; Dynamic mFunction; int mThreadNumber; Deque *mDeque; }; THREAD_FUNC_TYPE hxThreadFunc( void *inInfo ) { // info[1] will the the "top of stack" - values under this // (ie info[0] and other stack values) will be in the GC conservative range hxThreadInfo *info[2]; info[0] = (hxThreadInfo *)inInfo; info[1] = 0; tlsCurrentThread = info[0]; hx::SetTopOfStack((int *)&info[1], true); // Release the creation function info[0]->mSemaphore->Set(); // Call the debugger function to annouce that a thread has been created //__hxcpp_dbg_threadCreatedOrTerminated(info[0]->GetThreadNumber(), true); if ( info[0]->mFunction.GetPtr() ) { // Try ... catch info[0]->mFunction->__run(); } // Call the debugger function to annouce that a thread has terminated //__hxcpp_dbg_threadCreatedOrTerminated(info[0]->GetThreadNumber(), false); hx::UnregisterCurrentThread(); tlsCurrentThread = 0; THREAD_FUNC_RET } #ifdef KORE_CONSOLE Dynamic __hxcpp_thread_create(Dynamic inStart) { return hx::Throw(HX_CSTRING("Threads are not yet supported on consoles")); } #elif defined(HX_WINRT) Dynamic __hxcpp_thread_create(Dynamic inStart) { return hx::Throw(HX_CSTRING("Threads are not yet supported on WinRT")); } #else Dynamic __hxcpp_thread_create(Dynamic inStart) { #ifdef EMSCRIPTEN return hx::Throw( HX_CSTRING("Threads are not supported on Emscripten") ); #else g_threadInfoMutex.Lock(); int threadNumber = g_nextThreadNumber++; g_threadInfoMutex.Unlock(); hxThreadInfo *info = new hxThreadInfo(inStart, threadNumber); hx::GCPrepareMultiThreaded(); hx::EnterGCFreeZone(); bool ok = HxCreateDetachedThread(hxThreadFunc, info); if (ok) { info->mSemaphore->Wait(); } hx::ExitGCFreeZone(); info->CleanSemaphore(); if (!ok) throw Dynamic( HX_CSTRING("Could not create thread") ); return info; #endif } #endif #ifdef HXCPP_THREAD_INFO_PTHREAD static pthread_key_t externThreadInfoKey;; static pthread_once_t key_once = PTHREAD_ONCE_INIT; static void destroyThreadInfo(void *i) { hx::Object **threadRoot = (hx::Object **)i; hx::GCRemoveRoot(threadRoot); delete threadRoot; } static void make_key() { pthread_key_create(&externThreadInfoKey, destroyThreadInfo); } #elif defined(HXCPP_THREAD_INFO_LOCAL) struct ThreadInfoHolder { hx::Object **threadRoot; ThreadInfoHolder() : threadRoot(0) { } ~ThreadInfoHolder() { if (threadRoot) { hx::GCRemoveRoot(threadRoot); delete threadRoot; } } void set(hx::Object **info) { threadRoot = info; } hxThreadInfo *get() { return threadRoot ? (hxThreadInfo *)*threadRoot : nullptr; } }; static thread_local ThreadInfoHolder threadHolder; #else static hx::Object **sMainThreadInfoRoot = 0; #endif static hxThreadInfo *GetCurrentInfo(bool createNew = true) { hxThreadInfo *info = tlsCurrentThread; if (!info) { #ifdef HXCPP_THREAD_INFO_PTHREAD pthread_once(&key_once, make_key); hxThreadInfo **pp = (hxThreadInfo **)pthread_getspecific(externThreadInfoKey); if (pp) info = *pp; #elif defined(HXCPP_THREAD_INFO_LOCAL) info = threadHolder.get(); #else if (sMainThreadInfoRoot) info = (hxThreadInfo *)*sMainThreadInfoRoot; #endif } if (!info && createNew) { // New, non-haxe thread - might be the first thread, or might be a new // foreign thread. info = new hxThreadInfo(null(), 0); hx::Object **threadRoot = new hx::Object *; *threadRoot = info; hx::GCAddRoot(threadRoot); #ifdef HXCPP_THREAD_INFO_PTHREAD pthread_setspecific(externThreadInfoKey, threadRoot); #elif defined(HXCPP_THREAD_INFO_LOCAL) threadHolder.set(threadRoot); #else sMainThreadInfoRoot = threadRoot; #endif } return info; } Dynamic __hxcpp_thread_current() { return GetCurrentInfo(); } void __hxcpp_thread_send(Dynamic inThread, Dynamic inMessage) { hxThreadInfo *info = dynamic_cast(inThread.mPtr); if (!info) throw HX_INVALID_OBJECT; info->Send(inMessage); } Dynamic __hxcpp_thread_read_message(bool inBlocked) { hxThreadInfo *info = GetCurrentInfo(); return info->ReadMessage(inBlocked); } bool __hxcpp_is_current_thread(hx::Object *inThread) { hxThreadInfo *info = tlsCurrentThread; return info==inThread; } // --- TLS ------------------------------------------------------------ Dynamic __hxcpp_tls_get(int inID) { return GetCurrentInfo()->GetTLS(inID); } void __hxcpp_tls_set(int inID,Dynamic inVal) { GetCurrentInfo()->SetTLS(inID,inVal); } // --- Mutex ------------------------------------------------------------ class hxMutex : public hx::Object { public: hxMutex() { mFinalizer = new hx::InternalFinalizer(this); mFinalizer->mFinalizer = clean; } HX_IS_INSTANCE_OF enum { _hx_ClassId = hx::clsIdMutex }; #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { mFinalizer->Visit(__inCtx); } #endif hx::InternalFinalizer *mFinalizer; static void clean(hx::Object *inObj) { hxMutex *m = dynamic_cast(inObj); if (m) m->mMutex.Clean(); } bool Try() { return mMutex.TryLock(); } void Acquire() { hx::EnterGCFreeZone(); mMutex.Lock(); hx::ExitGCFreeZone(); } void Release() { mMutex.Unlock(); } HxMutex mMutex; }; Dynamic __hxcpp_mutex_create() { return new hxMutex; } void __hxcpp_mutex_acquire(Dynamic inMutex) { hxMutex *mutex = dynamic_cast(inMutex.mPtr); if (!mutex) throw HX_INVALID_OBJECT; mutex->Acquire(); } bool __hxcpp_mutex_try(Dynamic inMutex) { hxMutex *mutex = dynamic_cast(inMutex.mPtr); if (!mutex) throw HX_INVALID_OBJECT; return mutex->Try(); } void __hxcpp_mutex_release(Dynamic inMutex) { hxMutex *mutex = dynamic_cast(inMutex.mPtr); if (!mutex) throw HX_INVALID_OBJECT; return mutex->Release(); } #if defined(HX_LINUX) || defined(HX_ANDROID) #define POSIX_SEMAPHORE #include #endif #if defined(HX_MACOS) || defined(IPHONE) || defined(APPLETV) #define APPLE_SEMAPHORE #include #endif class hxSemaphore : public hx::Object { public: hx::InternalFinalizer *mFinalizer; #ifdef HX_WINDOWS HANDLE sem; #elif defined (POSIX_SEMAPHORE) sem_t sem; #elif defined(APPLE_SEMAPHORE) dispatch_semaphore_t sem; #endif bool valid; hxSemaphore(int value) { mFinalizer = new hx::InternalFinalizer(this); mFinalizer->mFinalizer = clean; #ifdef HX_WINDOWS sem = CreateSemaphoreW(NULL, value, 0x7FFFFFFF, NULL); #elif defined(POSIX_SEMAPHORE) sem_init(&sem, 0, value); #elif defined(APPLE_SEMAPHORE) sem = dispatch_semaphore_create(value); #endif valid = true; } HX_IS_INSTANCE_OF enum { _hx_ClassId = hx::clsIdSemaphore }; #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { mFinalizer->Visit(__inCtx); } #endif void Acquire() { #if HX_WINDOWS WaitForSingleObject(sem, INFINITE); #elif defined(POSIX_SEMAPHORE) sem_wait(&sem); #elif defined(APPLE_SEMAPHORE) dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); #endif } bool TryAcquire(double timeout) { #ifdef HX_WINDOWS return WaitForSingleObject(sem, (DWORD)((FLOAT)timeout * 1000.0)) == 0; #elif defined(POSIX_SEMAPHORE) if (timeout == 0) { return sem_trywait(&sem) == 0; } else { struct timeval tv; struct timespec t; double delta = timeout; int idelta = (int)delta, idelta2; delta -= idelta; delta *= 1.0e9; gettimeofday(&tv, NULL); delta += tv.tv_usec * 1000.0; idelta2 = (int)(delta / 1e9); delta -= idelta2 * 1e9; t.tv_sec = tv.tv_sec + idelta + idelta2; t.tv_nsec = (long)delta; return sem_timedwait(&sem, &t) == 0; } #elif defined(APPLE_SEMAPHORE) return dispatch_semaphore_wait( sem, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * 1000 * 1000 * 1000))) == 0; #else return false; #endif } void Release() { #if HX_WINDOWS ReleaseSemaphore(sem, 1, NULL); #elif defined(POSIX_SEMAPHORE) sem_post(&sem); #elif defined(APPLE_SEMAPHORE) dispatch_semaphore_signal(sem); #endif } static void clean(hx::Object *inObj) { hxSemaphore *l = dynamic_cast(inObj); if (l) { if(l->valid) { #ifdef HX_WINDOWS CloseHandle(l->sem); #elif defined(POSIX_SEMAPHORE) sem_destroy(&l->sem); #endif l->valid = false; } } } }; Dynamic __hxcpp_semaphore_create(int value) { return new hxSemaphore(value); } void __hxcpp_semaphore_acquire(Dynamic inSemaphore) { hxSemaphore *semaphore = dynamic_cast(inSemaphore.mPtr); if (!semaphore) throw HX_INVALID_OBJECT; semaphore->Acquire(); } bool __hxcpp_semaphore_try_acquire(Dynamic inSemaphore, double timeout) { hxSemaphore *semaphore = dynamic_cast(inSemaphore.mPtr); if (!semaphore) throw HX_INVALID_OBJECT; return semaphore->TryAcquire(timeout); } void __hxcpp_semaphore_release(Dynamic inSemaphore) { hxSemaphore *semaphore = dynamic_cast(inSemaphore.mPtr); if (!semaphore) throw HX_INVALID_OBJECT; semaphore->Release(); } class hxCondition : public hx::Object { public: #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT CRITICAL_SECTION cs; CONDITION_VARIABLE cond; #endif #else pthread_cond_t cond; pthread_mutex_t mutex; #endif hx::InternalFinalizer *mFinalizer; hxCondition() { mFinalizer = new hx::InternalFinalizer(this); mFinalizer->mFinalizer = clean; #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT InitializeCriticalSection(&cs); InitializeConditionVariable(&cond); #else throw Dynamic(HX_CSTRING("Condition variables are not supported on Windows XP")); #endif #else pthread_condattr_t cond_attr; pthread_condattr_init(&cond_attr); pthread_cond_init(&cond, &cond_attr); pthread_condattr_destroy(&cond_attr); pthread_mutexattr_t mutex_attr; pthread_mutexattr_init(&mutex_attr); pthread_mutex_init(&mutex, &mutex_attr); pthread_mutexattr_destroy(&mutex_attr); #endif } HX_IS_INSTANCE_OF enum { _hx_ClassId = hx::clsIdCondition }; #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { mFinalizer->Visit(__inCtx); } #endif static void clean(hx::Object *inObj) { hxCondition *cond = dynamic_cast(inObj); if (cond) { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT DeleteCriticalSection(&cond->cs); #endif #else pthread_cond_destroy(&cond->cond); pthread_mutex_destroy(&cond->mutex); #endif } } void Acquire() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT EnterCriticalSection(&cs); #endif #else pthread_mutex_lock(&mutex); #endif } bool TryAcquire() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT return (bool)TryEnterCriticalSection(&cs); #else return false; #endif #else return pthread_mutex_trylock(&mutex); #endif } void Release() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT LeaveCriticalSection(&cs); #endif #else pthread_mutex_unlock(&mutex); #endif } void Wait() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT SleepConditionVariableCS(&cond,&cs,INFINITE); #endif #else pthread_cond_wait(&cond, &mutex); #endif } bool TimedWait(double timeout) { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT return (bool)SleepConditionVariableCS(&cond, &cs, (DWORD)((FLOAT)timeout * 1000.0)); #else return false; #endif #else struct timeval tv; struct timespec t; double delta = timeout; int idelta = (int)delta, idelta2; delta -= idelta; delta *= 1.0e9; gettimeofday(&tv, NULL); delta += tv.tv_usec * 1000.0; idelta2 = (int)(delta / 1e9); delta -= idelta2 * 1e9; t.tv_sec = tv.tv_sec + idelta + idelta2; t.tv_nsec = (long)delta; return pthread_cond_timedwait(&cond, &mutex, &t); #endif } void Signal() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT WakeConditionVariable(&cond); #endif #else pthread_cond_signal(&cond); #endif } void Broadcast() { #ifdef HX_WINDOWS #ifndef HXCPP_WINXP_COMPAT WakeAllConditionVariable(&cond); #endif #else pthread_cond_broadcast(&cond); #endif } }; Dynamic __hxcpp_condition_create(void) { return new hxCondition; } void __hxcpp_condition_acquire(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; cond->Acquire(); } bool __hxcpp_condition_try_acquire(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; return cond->TryAcquire(); } void __hxcpp_condition_release(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; cond->Release(); } void __hxcpp_condition_wait(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; cond->Wait(); } bool __hxcpp_condition_timed_wait(Dynamic inCond, double timeout) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; return cond->TimedWait(timeout); } void __hxcpp_condition_signal(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; cond->Signal(); } void __hxcpp_condition_broadcast(Dynamic inCond) { hxCondition *cond = dynamic_cast(inCond.mPtr); if (!cond) throw HX_INVALID_OBJECT; cond->Broadcast(); } // --- Lock ------------------------------------------------------------ class hxLock : public hx::Object { public: hxLock() { mFinalizer = new hx::InternalFinalizer(this); mFinalizer->mFinalizer = clean; } HX_IS_INSTANCE_OF enum { _hx_ClassId = hx::clsIdLock }; #ifdef HXCPP_VISIT_ALLOCS void __Visit(hx::VisitContext *__inCtx) { mFinalizer->Visit(__inCtx); } #endif hx::InternalFinalizer *mFinalizer; double Now() { return kinc_time(); } static void clean(hx::Object *inObj) { hxLock *l = dynamic_cast(inObj); if (l) { l->mNotEmpty.Clean(); l->mAvailableLock.Clean(); } } bool Wait(double inTimeout) { double stop = 0; if (inTimeout>=0) stop = Now() + inTimeout; while(1) { mAvailableLock.Lock(); if (mAvailable) { --mAvailable; if (mAvailable>0) mNotEmpty.Set(); mAvailableLock.Unlock(); return true; } mAvailableLock.Unlock(); double wait = 0; if (inTimeout>=0) { wait = stop-Now(); if (wait<=0) return false; } hx::EnterGCFreeZone(); if (inTimeout<0) mNotEmpty.Wait( ); else mNotEmpty.WaitSeconds(wait); hx::ExitGCFreeZone(); } } void Release() { AutoLock lock(mAvailableLock); mAvailable++; mNotEmpty.Set(); } HxSemaphore mNotEmpty; HxMutex mAvailableLock; int mAvailable; }; Dynamic __hxcpp_lock_create() { return new hxLock; } bool __hxcpp_lock_wait(Dynamic inlock,double inTime) { hxLock *lock = dynamic_cast(inlock.mPtr); if (!lock) throw HX_INVALID_OBJECT; return lock->Wait(inTime); } void __hxcpp_lock_release(Dynamic inlock) { hxLock *lock = dynamic_cast(inlock.mPtr); if (!lock) throw HX_INVALID_OBJECT; lock->Release(); } int __hxcpp_GetCurrentThreadNumber() { // Can't allow GetCurrentInfo() to create the main thread's info // because that can cause a call loop. hxThreadInfo *threadInfo = GetCurrentInfo(false); if (!threadInfo) { return 0; } return threadInfo->GetThreadNumber(); } // --- Atomic --- bool _hx_atomic_exchange_if(::cpp::Pointer inPtr, int test, int newVal ) { return _hx_atomic_compare_exchange(inPtr, test, newVal) == test; } int _hx_atomic_inc(::cpp::Pointer inPtr ) { return _hx_atomic_add(inPtr, 1); } int _hx_atomic_dec(::cpp::Pointer inPtr ) { return _hx_atomic_sub(inPtr, 1); }