// 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