// Copyright 2016 The SwiftShader Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef rr_Thread_hpp #define rr_Thread_hpp #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #include <intrin.h> #else #include <pthread.h> #include <sched.h> #include <unistd.h> #define TLS_OUT_OF_INDEXES (pthread_key_t)(~0) #endif #include <stdlib.h> #if defined(__clang__) #if __has_include(<atomic>) // clang has an explicit check for the availability of atomic #define USE_STD_ATOMIC 1 #endif // atomic is available in C++11 or newer, and in Visual Studio 2012 or newer #elif (defined(_MSC_VER) && (_MSC_VER >= 1700)) || (__cplusplus >= 201103L) #define USE_STD_ATOMIC 1 #endif #if USE_STD_ATOMIC #include <atomic> #endif namespace rr { class Event; class Thread { public: Thread(void (*threadFunction)(void *parameters), void *parameters); ~Thread(); void join(); static void yield(); static void sleep(int milliseconds); #if defined(_WIN32) typedef DWORD LocalStorageKey; #else typedef pthread_key_t LocalStorageKey; #endif static LocalStorageKey allocateLocalStorageKey(void (*destructor)(void *storage) = free); static void freeLocalStorageKey(LocalStorageKey key); static void *allocateLocalStorage(LocalStorageKey key, size_t size); static void *getLocalStorage(LocalStorageKey key); static void freeLocalStorage(LocalStorageKey key); private: struct Entry { void (*const threadFunction)(void *parameters); void *threadParameters; Event *init; }; #if defined(_WIN32) static unsigned long __stdcall startFunction(void *parameters); HANDLE handle; #else static void *startFunction(void *parameters); pthread_t handle; #endif bool hasJoined = false; }; class Event { friend class Thread; public: Event(); ~Event(); void signal(); void wait(); private: #if defined(_WIN32) HANDLE handle; #else pthread_cond_t handle; pthread_mutex_t mutex; volatile bool signaled; #endif }; #if PERF_PROFILE int64_t atomicExchange(int64_t volatile *target, int64_t value); int atomicExchange(int volatile *target, int value); #endif int atomicIncrement(int volatile *value); int atomicDecrement(int volatile *value); int atomicAdd(int volatile *target, int value); void nop(); } namespace rr { inline void Thread::yield() { #if defined(_WIN32) Sleep(0); #elif defined(__APPLE__) pthread_yield_np(); #else sched_yield(); #endif } inline void Thread::sleep(int milliseconds) { #if defined(_WIN32) Sleep(milliseconds); #else usleep(1000 * milliseconds); #endif } inline Thread::LocalStorageKey Thread::allocateLocalStorageKey(void (*destructor)(void *storage)) { #if defined(_WIN32) return TlsAlloc(); #else LocalStorageKey key; pthread_key_create(&key, destructor); return key; #endif } inline void Thread::freeLocalStorageKey(LocalStorageKey key) { #if defined(_WIN32) TlsFree(key); #else pthread_key_delete(key); // Using an invalid key is an error but not undefined behavior. #endif } inline void *Thread::allocateLocalStorage(LocalStorageKey key, size_t size) { if(key == TLS_OUT_OF_INDEXES) { return nullptr; } freeLocalStorage(key); void *storage = malloc(size); #if defined(_WIN32) TlsSetValue(key, storage); #else pthread_setspecific(key, storage); #endif return storage; } inline void *Thread::getLocalStorage(LocalStorageKey key) { #if defined(_WIN32) return TlsGetValue(key); #else if(key == TLS_OUT_OF_INDEXES) // Avoid undefined behavior. { return nullptr; } return pthread_getspecific(key); #endif } inline void Thread::freeLocalStorage(LocalStorageKey key) { free(getLocalStorage(key)); #if defined(_WIN32) TlsSetValue(key, nullptr); #else pthread_setspecific(key, nullptr); #endif } inline void Event::signal() { #if defined(_WIN32) SetEvent(handle); #else pthread_mutex_lock(&mutex); signaled = true; pthread_cond_signal(&handle); pthread_mutex_unlock(&mutex); #endif } inline void Event::wait() { #if defined(_WIN32) WaitForSingleObject(handle, INFINITE); #else pthread_mutex_lock(&mutex); while(!signaled) pthread_cond_wait(&handle, &mutex); signaled = false; pthread_mutex_unlock(&mutex); #endif } #if PERF_PROFILE inline int64_t atomicExchange(volatile int64_t *target, int64_t value) { #if defined(_WIN32) return InterlockedExchange64(target, value); #else int ret; __asm__ __volatile__("lock; xchg8 %x0,(%x1)" : "=r" (ret) :"r" (target), "0" (value) : "memory" ); return ret; #endif } inline int atomicExchange(volatile int *target, int value) { #if defined(_WIN32) return InterlockedExchange((volatile long*)target, (long)value); #else int ret; __asm__ __volatile__("lock; xchgl %x0,(%x1)" : "=r" (ret) :"r" (target), "0" (value) : "memory" ); return ret; #endif } #endif inline int atomicIncrement(volatile int *value) { #if defined(_WIN32) return InterlockedIncrement((volatile long*)value); #else return __sync_add_and_fetch(value, 1); #endif } inline int atomicDecrement(volatile int *value) { #if defined(_WIN32) return InterlockedDecrement((volatile long*)value); #else return __sync_sub_and_fetch(value, 1); #endif } inline int atomicAdd(volatile int* target, int value) { #if defined(_WIN32) return InterlockedExchangeAdd((volatile long*)target, value) + value; #else return __sync_add_and_fetch(target, value); #endif } inline void nop() { #if defined(_WIN32) __nop(); #else __asm__ __volatile__ ("nop"); #endif } #if USE_STD_ATOMIC class AtomicInt { public: AtomicInt() : ai() {} AtomicInt(int i) : ai(i) {} inline operator int() const { return ai.load(std::memory_order_acquire); } inline void operator=(const AtomicInt& i) { ai.store(i.ai.load(std::memory_order_acquire), std::memory_order_release); } inline void operator=(int i) { ai.store(i, std::memory_order_release); } inline void operator--() { ai.fetch_sub(1, std::memory_order_acq_rel); } inline void operator++() { ai.fetch_add(1, std::memory_order_acq_rel); } inline int operator--(int) { return ai.fetch_sub(1, std::memory_order_acq_rel) - 1; } inline int operator++(int) { return ai.fetch_add(1, std::memory_order_acq_rel) + 1; } inline void operator-=(int i) { ai.fetch_sub(i, std::memory_order_acq_rel); } inline void operator+=(int i) { ai.fetch_add(i, std::memory_order_acq_rel); } private: std::atomic<int> ai; }; #else class AtomicInt { public: AtomicInt() {} AtomicInt(int i) : vi(i) {} inline operator int() const { return vi; } // Note: this isn't a guaranteed atomic operation inline void operator=(const AtomicInt& i) { atomicExchange(&vi, i.vi); } inline void operator=(int i) { atomicExchange(&vi, i); } inline void operator--() { atomicDecrement(&vi); } inline void operator++() { atomicIncrement(&vi); } inline int operator--(int) { return atomicDecrement(&vi); } inline int operator++(int) { return atomicIncrement(&vi); } inline void operator-=(int i) { atomicAdd(&vi, -i); } inline void operator+=(int i) { atomicAdd(&vi, i); } private: volatile int vi; }; #endif } #endif // rr_Thread_hpp