#define _GNU_SOURCE
#include <pthread.h>
#include <emscripten/threading.h>
#include <emscripten.h>
#include <sys/time.h>
#include <dirent.h>
#include <utime.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <termios.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include "../internal/pthread_impl.h"
#include <assert.h>

// With LLVM 3.6, C11 is the default compilation mode.
// gets() is deprecated under that standard, but emcc
// still provides it, so always include it in the build.
#if __STDC_VERSION__ >= 201112L
char *gets(char *);
#endif

int _pthread_getcanceltype()
{
	return pthread_self()->cancelasync;
}

static void inline __pthread_mutex_locked(pthread_mutex_t *mutex)
{
	// The lock is now ours, mark this thread as the owner of this lock.
	assert(mutex);
	assert(mutex->_m_lock == 0);
	mutex->_m_lock = pthread_self()->tid;
	if (_pthread_getcanceltype() == PTHREAD_CANCEL_ASYNCHRONOUS) pthread_testcancel();
}

double _pthread_msecs_until(const struct timespec *restrict at)
{
	struct timeval t;
	gettimeofday(&t, NULL);
	double cur_t = t.tv_sec * 1e3 + t.tv_usec * 1e-3;
	double at_t = at->tv_sec * 1e3 + at->tv_nsec * 1e-6;
	double msecs = at_t - cur_t;
	return msecs;
}

#if 0
int pthread_mutex_lock(pthread_mutex_t *mutex)
{
	if (!mutex) return EINVAL;
	assert(pthread_self() != 0);
	assert(pthread_self()->tid != 0);

	if (mutex->_m_lock == pthread_self()->tid) {
		if ((mutex->_m_type&3) == PTHREAD_MUTEX_RECURSIVE) {
			if ((unsigned)mutex->_m_count >= INT_MAX) return EAGAIN;
			++mutex->_m_count;
			return 0;
		} else if ((mutex->_m_type&3) == PTHREAD_MUTEX_ERRORCHECK) {
			return EDEADLK;
		}
	}

	int threadCancelType = _pthread_getcanceltype();

	int c = emscripten_atomic_cas_u32(&mutex->_m_addr, 0, 1);
	if (c != 0) {
		do {
			if (c == 2 || emscripten_atomic_cas_u32(&mutex->_m_addr, 1, 2) != 0) {
				double msecs = INFINITY;
				if (threadCancelType == PTHREAD_CANCEL_ASYNCHRONOUS) {
					// Sleep in small slices so that we can test cancellation to honor PTHREAD_CANCEL_ASYNCHRONOUS.
					pthread_testcancel();
					msecs = 100;
				}
				emscripten_futex_wait(&mutex->_m_addr, 2, msecs);
			}
		} while((c = emscripten_atomic_cas_u32(&mutex->_m_addr, 0, 2)));
	}

	__pthread_mutex_locked(mutex);
	return 0;
}

int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
	if (!mutex) return EINVAL;
	assert(pthread_self() != 0);

	if (mutex->_m_type != PTHREAD_MUTEX_NORMAL) {
		if (mutex->_m_lock != pthread_self()->tid) return EPERM;
		if ((mutex->_m_type&3) == PTHREAD_MUTEX_RECURSIVE && mutex->_m_count) {
			--mutex->_m_count;
			return 0;
		}
	}

	mutex->_m_lock = 0;
	if (emscripten_atomic_sub_u32((uint32_t*)&mutex->_m_addr, 1) != 1)
	{
		emscripten_atomic_store_u32((uint32_t*)&mutex->_m_addr, 0);
		emscripten_futex_wake((uint32_t*)&mutex->_m_addr, 1);
	}
	return 0;
}

int pthread_mutex_trylock(pthread_mutex_t *mutex)
{
	if (!mutex) return EINVAL;
	if (mutex->_m_lock == pthread_self()->tid) {
		if ((mutex->_m_type&3) == PTHREAD_MUTEX_RECURSIVE) {
			if ((unsigned)mutex->_m_count >= INT_MAX) return EAGAIN;
			++mutex->_m_count;
			return 0;
		} else if ((mutex->_m_type&3) == PTHREAD_MUTEX_ERRORCHECK) {
			return EDEADLK;
		}
	}

	if (emscripten_atomic_cas_u32(&mutex->_m_addr, 0, 1) == 0) {
		__pthread_mutex_locked(mutex);
		return 0;
	}
	else
		return EBUSY;
}

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict at)
{
	if (!mutex || !at) return EINVAL;
	if (mutex->_m_lock == pthread_self()->tid) {
		if ((mutex->_m_type&3) == PTHREAD_MUTEX_RECURSIVE) {
			if ((unsigned)mutex->_m_count >= INT_MAX) return EAGAIN;
			++mutex->_m_count;
			return 0;
		} else if ((mutex->_m_type&3) == PTHREAD_MUTEX_ERRORCHECK) {
			return EDEADLK;
		}
	}

	int threadCancelType = _pthread_getcanceltype();
	int c = emscripten_atomic_cas_u32(&mutex->_m_addr, 0, 1);
	if (c != 0) {
		do {
			if (c == 2 || emscripten_atomic_cas_u32(&mutex->_m_addr, 1, 2) != 0)
			{
				if (at->tv_nsec < 0 || at->tv_nsec >= 1000000000) return EINVAL;
				double msecs = _pthread_msecs_until(at);
				if (msecs <= 0) return ETIMEDOUT;

				// Sleep in small slices if thread type is PTHREAD_CANCEL_ASYNCHRONOUS
				// so that we can honor PTHREAD_CANCEL_ASYNCHRONOUS requests.
				if (threadCancelType == PTHREAD_CANCEL_ASYNCHRONOUS) {
					pthread_testcancel();
					if (msecs > 100) msecs = 100;
				}
				int ret = emscripten_futex_wait(&mutex->_m_addr, 2, msecs);
				if (ret == 0) break;
				else if (threadCancelType != PTHREAD_CANCEL_ASYNCHRONOUS || _pthread_msecs_until(at) <= 0) {
					return ETIMEDOUT;
				}
			}
		} while((c = emscripten_atomic_cas_u32(&mutex->_m_addr, 0, 2)));
	}

	__pthread_mutex_locked(mutex);
	return 0;
}
#endif

int sched_get_priority_max(int policy)
{
	// Web workers do not actually support prioritizing threads,
	// but mimic values that Linux apparently reports, see
	// http://man7.org/linux/man-pages/man2/sched_get_priority_min.2.html
	if (policy == SCHED_FIFO || policy == SCHED_RR)
		return 99;
	else
		return 0;
}

int sched_get_priority_min(int policy)
{
	// Web workers do not actually support prioritizing threads,
	// but mimic values that Linux apparently reports, see
	// http://man7.org/linux/man-pages/man2/sched_get_priority_min.2.html
	if (policy == SCHED_FIFO || policy == SCHED_RR)
		return 1;
	else
		return 0;
}

int pthread_setcancelstate(int new, int *old)
{
	if (new > 1U) return EINVAL;
	struct pthread *self = pthread_self();
	if (old) *old = self->canceldisable;
	self->canceldisable = new;
	return 0;
}

int _pthread_isduecanceled(struct pthread *pthread_ptr)
{
	return pthread_ptr->threadStatus == 2/*canceled*/;
}

void pthread_testcancel()
{
	struct pthread *self = pthread_self();
	if (self->canceldisable) return;
	if (_pthread_isduecanceled(self)) {
		EM_ASM( throw 'Canceled!'; );
	}
}

int pthread_getattr_np(pthread_t t, pthread_attr_t *a)
{
	*a = (pthread_attr_t){0};
	a->_a_detach = !!t->detached;
	a->_a_stackaddr = (uintptr_t)t->stack;
	a->_a_stacksize = t->stack_size - DEFAULT_STACK_SIZE;
	return 0;
}

static uint32_t dummyZeroAddress = 0;

int usleep(unsigned usec)
{
	int is_main_thread = emscripten_is_main_runtime_thread();
	double now = emscripten_get_now();
	double target = now + usec * 1e-3;
	while(now < target) {
		if (is_main_thread) emscripten_main_thread_process_queued_calls(); // Assist other threads by executing proxied operations that are effectively singlethreaded.
		pthread_testcancel(); // pthreads spec: usleep is a cancellation point, so it must test if this thread is cancelled during the sleep.
		now = emscripten_get_now();
		double msecsToSleep = target - now;
		if (msecsToSleep > 1.0) {
			if (msecsToSleep > 100.0) msecsToSleep = 100.0;
			if (is_main_thread && msecsToSleep > 1) msecsToSleep = 1; // main thread may need to run proxied calls, so sleep in very small slices to be responsive.
			emscripten_futex_wait(&dummyZeroAddress, 0, msecsToSleep);
		}
	}
	return 0;
}

static void _do_call(em_queued_call *q)
{
	switch(q->function)
	{
		case EM_PROXIED_UTIME: q->returnValue.i = utime(q->args[0].cp, (struct utimbuf*)q->args[1].vp); break;
		case EM_PROXIED_UTIMES: q->returnValue.i = utimes(q->args[0].cp, (struct timeval*)q->args[1].vp); break;
		case EM_PROXIED_CHROOT: q->returnValue.i = chroot(q->args[0].cp); break;
		case EM_PROXIED_FPATHCONF: q->returnValue.i = fpathconf(q->args[0].i, q->args[1].i); break;
		case EM_PROXIED_CONFSTR: q->returnValue.i = confstr(q->args[0].i, q->args[1].cp, q->args[2].i); break;
		case EM_PROXIED_SYSCONF: q->returnValue.i = sysconf(q->args[0].i); break;
		case EM_PROXIED_SBRK: q->returnValue.vp = sbrk(q->args[0].i); break;
		case EM_PROXIED_ATEXIT: q->returnValue.i = atexit(q->args[0].vp); break;
		case EM_PROXIED_GETENV: q->returnValue.cp = getenv(q->args[0].cp); break;
		case EM_PROXIED_CLEARENV: q->returnValue.i = clearenv(); break;
		case EM_PROXIED_SETENV: q->returnValue.i = setenv(q->args[0].cp, q->args[1].cp, q->args[2].i); break;
		case EM_PROXIED_UNSETENV: q->returnValue.i = unsetenv(q->args[0].cp); break;
		case EM_PROXIED_PUTENV: q->returnValue.i = putenv(q->args[0].cp); break;
		case EM_PROXIED_TZSET: tzset(); break;
		case EM_PROXIED_PTHREAD_CREATE: q->returnValue.i = pthread_create(q->args[0].vp, q->args[1].vp, q->args[2].vp, q->args[3].vp); break;
		case EM_PROXIED_SYSCALL: q->returnValue.i = emscripten_syscall(q->args[0].i, q->args[1].vp); break;
		default: assert(0 && "Invalid Emscripten pthread _do_call opcode!");
	}
	q->operationDone = 1;
	emscripten_futex_wake(&q->operationDone, INT_MAX);
}

#define CALL_QUEUE_SIZE 128
static em_queued_call **call_queue = 0;
static int call_queue_length = 0; // Shared data synchronized by call_queue_lock.
static pthread_mutex_t call_queue_lock = PTHREAD_MUTEX_INITIALIZER;

void EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread(em_queued_call *call)
{
	assert(call);
	if (emscripten_is_main_runtime_thread()) {
		_do_call(call);
		return;
	}
	pthread_mutex_lock(&call_queue_lock);
	if (!call_queue) call_queue = malloc(sizeof(em_queued_call*) * CALL_QUEUE_SIZE); // Shared data synchronized by call_queue_lock.
	// Note: currently call_queue_length can be at most the number of pthreads that are currently running, so the queue can never get
	// full. However if/when the queue is extended to be asynchronous for void-returning functions later, this will need to be revised.
	assert(call_queue_length < CALL_QUEUE_SIZE);
	call_queue[call_queue_length] = call;
	++call_queue_length;
	if (call_queue_length == 1) {
		EM_ASM(postMessage({ cmd: 'processQueuedMainThreadWork' }));
	}
	pthread_mutex_unlock(&call_queue_lock);
	int r;
	do {
		r = emscripten_futex_wait(&call->operationDone, 0, INFINITY);
	} while(r != 0 && call->operationDone == 0);
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_1(int function, void *arg1)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_2(int function, void *arg1, void *arg2)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_xprintf_varargs(int function, int param0, const char *format, ...)
{
	va_list args;
	va_start(args, format);
	const int CAP = 128;
	char str[CAP];
	char *s = str;
	int len = vsnprintf(s, CAP, format, args);
	if (len >= CAP)
	{
		s = (char*)malloc(len+1);
		va_start(args, format);
		len = vsnprintf(s, len+1, format, args);
	}
	em_queued_call q = { function, 0 };
	q.args[0].vp = param0;
	q.args[1].vp = s;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	if (s != str) free(s);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_3(int function, void *arg1, void *arg2, void *arg3)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.args[2].vp = arg3;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_4(int function, void *arg1, void *arg2, void *arg3, void *arg4)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.args[2].vp = arg3;
	q.args[3].vp = arg4;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_5(int function, void *arg1, void *arg2, void *arg3, void *arg4, void *arg5)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.args[2].vp = arg3;
	q.args[3].vp = arg4;
	q.args[4].vp = arg5;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_6(int function, void *arg1, void *arg2, void *arg3, void *arg4, void *arg5, void *arg6)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.args[2].vp = arg3;
	q.args[3].vp = arg4;
	q.args[4].vp = arg5;
	q.args[5].vp = arg6;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

void * EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_7(int function, void *arg1, void *arg2, void *arg3, void *arg4, void *arg5, void *arg6, void *arg7)
{
	em_queued_call q = { function, 0 };
	q.args[0].vp = arg1;
	q.args[1].vp = arg2;
	q.args[2].vp = arg3;
	q.args[3].vp = arg4;
	q.args[4].vp = arg5;
	q.args[5].vp = arg6;
	q.args[6].vp = arg7;
	q.returnValue.vp = 0;
	emscripten_sync_run_in_main_thread(&q);
	return q.returnValue.vp;
}

static int bool_inside_nested_process_queued_calls = 0;

void EMSCRIPTEN_KEEPALIVE emscripten_main_thread_process_queued_calls()
{
	assert(emscripten_is_main_runtime_thread() && "emscripten_main_thread_process_queued_calls must be called from the main thread!");
	if (!emscripten_is_main_runtime_thread()) return;

	// It is possible that when processing a queued call, the call flow leads back to calling this function in a nested fashion!
	// Therefore this scenario must explicitly be detected, and processing the queue must be avoided if we are nesting, or otherwise
	// the same queued calls would be processed again and again.
	if (bool_inside_nested_process_queued_calls) return;
	// This must be before pthread_mutex_lock(), since pthread_mutex_lock() can call back to this function.
	bool_inside_nested_process_queued_calls = 1;
	pthread_mutex_lock(&call_queue_lock);
	for (int i = 0; i < call_queue_length; ++i)
		_do_call(call_queue[i]);
	call_queue_length = 0;
	bool_inside_nested_process_queued_calls = 0;
	pthread_mutex_unlock(&call_queue_lock);
}

float EMSCRIPTEN_KEEPALIVE emscripten_atomic_load_f32(const void *addr)
{
	union {
		float f;
		uint32_t u;
	} u;
	u.u = emscripten_atomic_load_u32(addr);
	return u.f;
}

// Use an array of multiple interleaved spinlock mutexes to separate memory addresses to ease pressure when locking.
// This is outright horrible, but enables easily porting code that does require 64-bit atomics.
// Eventually in the long run we'd hope to have real support for 64-bit atomics in the browser, after
// which this emulation can be removed.
#define NUM_64BIT_LOCKS 256
static int emulated64BitAtomicsLocks[NUM_64BIT_LOCKS] = {};

uint32_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_exchange_u32(void/*uint32_t*/ *addr, uint32_t newVal)
{
	uint32_t oldVal, oldVal2;
	do {
		oldVal = emscripten_atomic_load_u32(addr);
		oldVal2 = emscripten_atomic_cas_u32(addr, oldVal, newVal);
	} while (oldVal != oldVal2);
	return oldVal;
}

#define SPINLOCK_ACQUIRE(addr) do { while(emscripten_atomic_exchange_u32((void*)(addr), 1)) /*nop*/; } while(0)
#define SPINLOCK_RELEASE(addr) emscripten_atomic_store_u32((void*)(addr), 0)

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_exchange_u64(void/*uint64_t*/ *addr, uint64_t newVal)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldValInMemory = *(uint64_t*)addr;
	*(uint64_t*)addr = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldValInMemory;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_cas_u64(void/*uint64_t*/ *addr, uint64_t oldVal, uint64_t newVal)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldValInMemory = *(uint64_t*)addr;
	if (oldValInMemory == oldVal)
		*(uint64_t*)addr = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldValInMemory;
}

double EMSCRIPTEN_KEEPALIVE emscripten_atomic_load_f64(const void *addr)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	double val = *(double*)addr;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return val;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_load_u64(const void *addr)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t val = *(uint64_t*)addr;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return val;
}

float EMSCRIPTEN_KEEPALIVE emscripten_atomic_store_f32(void *addr, float val)
{
	union {
		float f;
		uint32_t u;
	} u;
	u.f = val;
	return emscripten_atomic_store_u32(addr, u.u);
}

double EMSCRIPTEN_KEEPALIVE emscripten_atomic_store_f64(void *addr, double val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	double *a = (double*)addr;
	*a = val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return val;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_store_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t*)addr;
	*a = val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return val;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_add_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t *)addr;
	uint64_t newVal = *a + val;
	*a = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return newVal;
}

// This variant is implemented for emulating GCC 64-bit __sync_fetch_and_add. Not to be called directly.
uint64_t EMSCRIPTEN_KEEPALIVE _emscripten_atomic_fetch_and_add_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldVal = *(uint64_t *)addr;
	*(uint64_t *)addr = oldVal + val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldVal;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_sub_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t *)addr;
	uint64_t newVal = *a - val;
	*a = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return newVal;
}

// This variant is implemented for emulating GCC 64-bit __sync_fetch_and_sub. Not to be called directly.
uint64_t EMSCRIPTEN_KEEPALIVE _emscripten_atomic_fetch_and_sub_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldVal = *(uint64_t *)addr;
	*(uint64_t *)addr = oldVal - val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldVal;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_and_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t *)addr;
	uint64_t newVal = *a & val;
	*a = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return newVal;
}

// This variant is implemented for emulating GCC 64-bit __sync_fetch_and_and. Not to be called directly.
uint64_t EMSCRIPTEN_KEEPALIVE _emscripten_atomic_fetch_and_and_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldVal = *(uint64_t *)addr;
	*(uint64_t *)addr = oldVal & val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldVal;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_or_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t *)addr;
	uint64_t newVal = *a | val;
	*a = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return newVal;
}

// This variant is implemented for emulating GCC 64-bit __sync_fetch_and_or. Not to be called directly.
uint64_t EMSCRIPTEN_KEEPALIVE _emscripten_atomic_fetch_and_or_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldVal = *(uint64_t *)addr;
	*(uint64_t *)addr = oldVal | val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldVal;
}

uint64_t EMSCRIPTEN_KEEPALIVE emscripten_atomic_xor_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t *a = (uint64_t *)addr;
	uint64_t newVal = *a ^ val;
	*a = newVal;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return newVal;
}

// This variant is implemented for emulating GCC 64-bit __sync_fetch_and_xor. Not to be called directly.
uint64_t EMSCRIPTEN_KEEPALIVE _emscripten_atomic_fetch_and_xor_u64(void *addr, uint64_t val)
{
	uintptr_t m = (uintptr_t)addr >> 3;
	SPINLOCK_ACQUIRE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	uint64_t oldVal = *(uint64_t *)addr;
	*(uint64_t *)addr = oldVal ^ val;
	SPINLOCK_RELEASE(&emulated64BitAtomicsLocks[m&(NUM_64BIT_LOCKS-1)]);
	return oldVal;
}

int llvm_memory_barrier()
{
	emscripten_atomic_fence();
}

int llvm_atomic_load_add_i32_p0i32(int *ptr, int delta)
{
	return emscripten_atomic_add_u32(ptr, delta) - delta;
}

uint64_t __atomic_load_8(void *ptr, int memmodel)
{
	return emscripten_atomic_load_u64(ptr);
}

uint64_t __atomic_store_8(void *ptr, uint64_t value, int memmodel)
{
	return emscripten_atomic_store_u64(ptr, value);
}

uint64_t __atomic_exchange_8(void *ptr, uint64_t value, int memmodel)
{
	return emscripten_atomic_exchange_u64(ptr, value);
}

uint64_t __atomic_compare_exchange_8(void *ptr, uint64_t *expected, uint64_t desired, int weak, int success_memmodel, int failure_memmodel)
{
	return emscripten_atomic_cas_u64(ptr, *expected, desired);
}

uint64_t __atomic_fetch_add_8(void *ptr, uint64_t value, int memmodel)
{
	return _emscripten_atomic_fetch_and_add_u64(ptr, value);
}

uint64_t __atomic_fetch_sub_8(void *ptr, uint64_t value, int memmodel)
{
	return _emscripten_atomic_fetch_and_sub_u64(ptr, value);
}

uint64_t __atomic_fetch_and_8(void *ptr, uint64_t value, int memmodel)
{
	return _emscripten_atomic_fetch_and_and_u64(ptr, value);
}

uint64_t __atomic_fetch_or_8(void *ptr, uint64_t value, int memmodel)
{
	return _emscripten_atomic_fetch_and_or_u64(ptr, value);
}

uint64_t __atomic_fetch_xor_8(void *ptr, uint64_t value, int memmodel)
{
	return _emscripten_atomic_fetch_and_xor_u64(ptr, value);
}
