/*
 * Copyright 2015 The Emscripten Authors.  All rights reserved.
 * Emscripten is available under two separate licenses, the MIT license and the
 * University of Illinois/NCSA Open Source License.  Both these licenses can be
 * found in the LICENSE file.
 */

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

#include <emscripten.h>
#include <emscripten/threading.h>
#include <emscripten/stack.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

// Extra pthread_attr_t field:
#define _a_transferredcanvases __u.__s[9]

void __pthread_testcancel();

int emscripten_pthread_attr_gettransferredcanvases(const pthread_attr_t* a, const char** str) {
  *str = (const char*)a->_a_transferredcanvases;
  return 0;
}

int emscripten_pthread_attr_settransferredcanvases(pthread_attr_t* a, const char* str) {
  a->_a_transferredcanvases = (int)str;
  return 0;
}

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();
}

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;

void emscripten_thread_sleep(double msecs) {
  double now = emscripten_get_now();
  double target = now + msecs;

  __pthread_testcancel(); // pthreads spec: sleep is a cancellation point, so must test if this
                          // thread is cancelled during the sleep.
  emscripten_current_thread_process_queued_calls();

  // If we have less than this many msecs left to wait, busy spin that instead.
  const double minimumTimeSliceToSleep = 0.1;

  // main thread may need to run proxied calls, so sleep in very small slices to be responsive.
  const double maxMsecsSliceToSleep = emscripten_is_main_browser_thread() ? 1 : 100;

  emscripten_conditional_set_current_thread_status(
    EM_THREAD_STATUS_RUNNING, EM_THREAD_STATUS_SLEEPING);
  now = emscripten_get_now();
  while (now < target) {
    // Keep processing the main loop of the calling thread.
    __pthread_testcancel(); // pthreads spec: sleep is a cancellation point, so must test if this
                            // thread is cancelled during the sleep.
    emscripten_current_thread_process_queued_calls();

    now = emscripten_get_now();
    double msecsToSleep = target - now;
    if (msecsToSleep > maxMsecsSliceToSleep)
      msecsToSleep = maxMsecsSliceToSleep;
    if (msecsToSleep >= minimumTimeSliceToSleep)
      emscripten_futex_wait(&dummyZeroAddress, 0, msecsToSleep);
    now = emscripten_get_now();
  };

  emscripten_conditional_set_current_thread_status(
    EM_THREAD_STATUS_SLEEPING, EM_THREAD_STATUS_RUNNING);
}

int nanosleep(const struct timespec* req, struct timespec* rem) {
  if (!req || req->tv_nsec < 0 || req->tv_nsec > 999999999L || req->tv_sec < 0) {
    errno = EINVAL;
    return -1;
  }
  emscripten_thread_sleep(req->tv_sec * 1000.0 + req->tv_nsec / 1e6);
  return 0;
}

int usleep(unsigned usec) {
  emscripten_thread_sleep(usec / 1e3);
  return 0;
}

// Allocator and deallocator for em_queued_call objects.
static em_queued_call* em_queued_call_malloc() {
  em_queued_call* call = (em_queued_call*)malloc(sizeof(em_queued_call));
  assert(call); // Not a programming error, but use assert() in debug builds to catch OOM scenarios.
  if (call) {
    call->operationDone = 0;
    call->functionPtr = 0;
    call->satelliteData = 0;
  }
  return call;
}
static void em_queued_call_free(em_queued_call* call) {
  if (call)
    free(call->satelliteData);
  free(call);
}

void emscripten_async_waitable_close(em_queued_call* call) { em_queued_call_free(call); }

extern double emscripten_receive_on_main_thread_js(int functionIndex, int numCallArgs, double* args);
extern int _emscripten_notify_thread_queue(pthread_t targetThreadId, pthread_t mainThreadId);

#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define HAS_ASAN
void __lsan_disable_in_this_thread(void);
void __lsan_enable_in_this_thread(void);
int emscripten_builtin_pthread_create(void *thread, void *attr,
                                      void *(*callback)(void *), void *arg);
#endif
#endif

static void _do_call(em_queued_call* q) {
  // C function pointer
  assert(EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(q->functionEnum) <= EM_QUEUED_CALL_MAX_ARGS);
  switch (q->functionEnum) {
    case EM_PROXIED_PTHREAD_CREATE:
#ifdef HAS_ASAN
      // ASan wraps the emscripten_builtin_pthread_create call in __lsan::ScopedInterceptorDisabler.
      // Unfortunately, that only disables it on the thread that made the call.
      // This is sufficient on the main thread.
      // On non-main threads, pthread_create gets proxied to the main thread, where LSan is not
      // disabled. This makes it necessary for us to disable LSan here, so that it does not detect
      // pthread's internal allocations as leaks.
      __lsan_disable_in_this_thread();
      q->returnValue.i =
        emscripten_builtin_pthread_create(q->args[0].vp, q->args[1].vp, q->args[2].vp, q->args[3].vp);
      __lsan_enable_in_this_thread();
#else
      q->returnValue.i =
        pthread_create(q->args[0].vp, q->args[1].vp, q->args[2].vp, q->args[3].vp);
#endif
      break;
    case EM_PROXIED_CREATE_CONTEXT:
      q->returnValue.i = emscripten_webgl_create_context(q->args[0].cp, q->args[1].vp);
      break;
    case EM_PROXIED_RESIZE_OFFSCREENCANVAS:
      q->returnValue.i =
        emscripten_set_canvas_element_size(q->args[0].cp, q->args[1].i, q->args[2].i);
      break;
    case EM_PROXIED_JS_FUNCTION:
      q->returnValue.d =
        emscripten_receive_on_main_thread_js((int)q->functionPtr, q->args[0].i, &q->args[1].d);
      break;
    case EM_FUNC_SIG_V:
      ((em_func_v)q->functionPtr)();
      break;
    case EM_FUNC_SIG_VI:
      ((em_func_vi)q->functionPtr)(q->args[0].i);
      break;
    case EM_FUNC_SIG_VF:
      ((em_func_vf)q->functionPtr)(q->args[0].f);
      break;
    case EM_FUNC_SIG_VII:
      ((em_func_vii)q->functionPtr)(q->args[0].i, q->args[1].i);
      break;
    case EM_FUNC_SIG_VIF:
      ((em_func_vif)q->functionPtr)(q->args[0].i, q->args[1].f);
      break;
    case EM_FUNC_SIG_VFF:
      ((em_func_vff)q->functionPtr)(q->args[0].f, q->args[1].f);
      break;
    case EM_FUNC_SIG_VIII:
      ((em_func_viii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i);
      break;
    case EM_FUNC_SIG_VIIF:
      ((em_func_viif)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].f);
      break;
    case EM_FUNC_SIG_VIFF:
      ((em_func_viff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f);
      break;
    case EM_FUNC_SIG_VFFF:
      ((em_func_vfff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f);
      break;
    case EM_FUNC_SIG_VIIII:
      ((em_func_viiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i);
      break;
    case EM_FUNC_SIG_VIIFI:
      ((em_func_viifi)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].f, q->args[3].i);
      break;
    case EM_FUNC_SIG_VIFFF:
      ((em_func_vifff)q->functionPtr)(q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f);
      break;
    case EM_FUNC_SIG_VFFFF:
      ((em_func_vffff)q->functionPtr)(q->args[0].f, q->args[1].f, q->args[2].f, q->args[3].f);
      break;
    case EM_FUNC_SIG_VIIIII:
      ((em_func_viiiii)q->functionPtr)(
        q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i);
      break;
    case EM_FUNC_SIG_VIFFFF:
      ((em_func_viffff)q->functionPtr)(
        q->args[0].i, q->args[1].f, q->args[2].f, q->args[3].f, q->args[4].f);
      break;
    case EM_FUNC_SIG_VIIIIII:
      ((em_func_viiiiii)q->functionPtr)(
        q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i);
      break;
    case EM_FUNC_SIG_VIIIIIII:
      ((em_func_viiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,
        q->args[4].i, q->args[5].i, q->args[6].i);
      break;
    case EM_FUNC_SIG_VIIIIIIII:
      ((em_func_viiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,
        q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i);
      break;
    case EM_FUNC_SIG_VIIIIIIIII:
      ((em_func_viiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i,
        q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i);
      break;
    case EM_FUNC_SIG_VIIIIIIIIII:
      ((em_func_viiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,
        q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i,
        q->args[9].i);
      break;
    case EM_FUNC_SIG_VIIIIIIIIIII:
      ((em_func_viiiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,
        q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i,
        q->args[9].i, q->args[10].i);
      break;
    case EM_FUNC_SIG_I:
      q->returnValue.i = ((em_func_i)q->functionPtr)();
      break;
    case EM_FUNC_SIG_II:
      q->returnValue.i = ((em_func_ii)q->functionPtr)(q->args[0].i);
      break;
    case EM_FUNC_SIG_III:
      q->returnValue.i = ((em_func_iii)q->functionPtr)(q->args[0].i, q->args[1].i);
      break;
    case EM_FUNC_SIG_IIII:
      q->returnValue.i = ((em_func_iiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i);
      break;
    case EM_FUNC_SIG_IIIII:
      q->returnValue.i =
        ((em_func_iiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i);
      break;
    case EM_FUNC_SIG_IIIIII:
      q->returnValue.i = ((em_func_iiiiii)q->functionPtr)(
        q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i);
      break;
    case EM_FUNC_SIG_IIIIIII:
      q->returnValue.i = ((em_func_iiiiiii)q->functionPtr)(
        q->args[0].i, q->args[1].i, q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i);
      break;
    case EM_FUNC_SIG_IIIIIIII:
      q->returnValue.i = ((em_func_iiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i,
        q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i);
      break;
    case EM_FUNC_SIG_IIIIIIIII:
      q->returnValue.i = ((em_func_iiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i,
        q->args[2].i, q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i);
      break;
    case EM_FUNC_SIG_IIIIIIIIII:
      q->returnValue.i =
        ((em_func_iiiiiiiiii)q->functionPtr)(q->args[0].i, q->args[1].i, q->args[2].i,
          q->args[3].i, q->args[4].i, q->args[5].i, q->args[6].i, q->args[7].i, q->args[8].i);
      break;
    default:
      assert(0 && "Invalid Emscripten pthread _do_call opcode!");
  }

  // If the caller is detached from this operation, it is the main thread's responsibility to free
  // up the call object.
  if (q->calleeDelete) {
    emscripten_async_waitable_close(q);
    // No need to wake a listener, nothing is listening to this since the call object is detached.
  } else {
    // The caller owns this call object, it is listening to it and will free it up.
    q->operationDone = 1;
    emscripten_futex_wake(&q->operationDone, INT_MAX);
  }
}

#define CALL_QUEUE_SIZE 128

typedef struct CallQueue {
  void* target_thread;
  em_queued_call** call_queue;
  int call_queue_head; // Shared data synchronized by call_queue_lock.
  int call_queue_tail;
  struct CallQueue* next;
} CallQueue;

// Currently global to the queue, but this can be improved to be per-queue specific. (TODO: with
// lockfree list operations on callQueue_head, or removing the list by moving this data to
// pthread_t)
static pthread_mutex_t call_queue_lock = PTHREAD_MUTEX_INITIALIZER;
static CallQueue* callQueue_head = 0;

static CallQueue* GetQueue(
  void* target) // Not thread safe, call while having call_queue_lock obtained.
{
  assert(target);
  CallQueue* q = callQueue_head;
  while (q && q->target_thread != target)
    q = q->next;
  return q;
}

static CallQueue* GetOrAllocateQueue(
  void* target) // Not thread safe, call while having call_queue_lock obtained.
{
  CallQueue* q = GetQueue(target);
  if (q)
    return q;

  q = (CallQueue*)malloc(sizeof(CallQueue));
  q->target_thread = target;
  q->call_queue = 0;
  q->call_queue_head = 0;
  q->call_queue_tail = 0;
  q->next = 0;
  if (callQueue_head) {
    CallQueue* last = callQueue_head;
    while (last->next)
      last = last->next;
    last->next = q;
  } else {
    callQueue_head = q;
  }
  return q;
}

EMSCRIPTEN_RESULT emscripten_wait_for_call_v(em_queued_call* call, double timeoutMSecs) {
  int r;

  int done = emscripten_atomic_load_u32(&call->operationDone);
  if (!done) {
    double now = emscripten_get_now();
    double waitEndTime = now + timeoutMSecs;
    emscripten_set_current_thread_status(EM_THREAD_STATUS_WAITPROXY);
    while (!done && now < waitEndTime) {
      r = emscripten_futex_wait(&call->operationDone, 0, waitEndTime - now);
      done = emscripten_atomic_load_u32(&call->operationDone);
      now = emscripten_get_now();
    }
    emscripten_set_current_thread_status(EM_THREAD_STATUS_RUNNING);
  }
  if (done)
    return EMSCRIPTEN_RESULT_SUCCESS;
  else
    return EMSCRIPTEN_RESULT_TIMED_OUT;
}

EMSCRIPTEN_RESULT emscripten_wait_for_call_i(
  em_queued_call* call, double timeoutMSecs, int* outResult) {
  EMSCRIPTEN_RESULT res = emscripten_wait_for_call_v(call, timeoutMSecs);
  if (res == EMSCRIPTEN_RESULT_SUCCESS && outResult)
    *outResult = call->returnValue.i;
  return res;
}

static pthread_t main_browser_thread_id_ = 0;

void EMSCRIPTEN_KEEPALIVE emscripten_register_main_browser_thread_id(
  pthread_t main_browser_thread_id) {
  main_browser_thread_id_ = main_browser_thread_id;
}

pthread_t EMSCRIPTEN_KEEPALIVE emscripten_main_browser_thread_id() {
  return main_browser_thread_id_;
}

int EMSCRIPTEN_KEEPALIVE do_emscripten_dispatch_to_thread(
  pthread_t target_thread, em_queued_call* call) {
  assert(call);

  // #if PTHREADS_DEBUG // TODO: Create a debug version of pthreads library
  //	EM_ASM_INT({dump('thread ' + _pthread_self() + ' (ENVIRONMENT_IS_WORKER: ' +
  //ENVIRONMENT_IS_WORKER + '), queueing call of function enum=' + $0 + '/ptr=' + $1 + ' on thread '
  //+ $2 + '\n' + new Error().stack)}, call->functionEnum, call->functionPtr, target_thread);
  // #endif

  // Can't be a null pointer here, but can't be EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD
  // either.
  assert(target_thread);
  if (target_thread == EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD)
    target_thread = emscripten_main_browser_thread_id();

  // If we are the target recipient of this message, we can just call the operation directly.
  if (target_thread == EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD ||
      target_thread == pthread_self()) {
    _do_call(call);
    return 1;
  }

  // Add the operation to the call queue of the main runtime thread.
  pthread_mutex_lock(&call_queue_lock);
  CallQueue* q = GetOrAllocateQueue(target_thread);
  if (!q->call_queue)
    q->call_queue = malloc(
      sizeof(em_queued_call*) * CALL_QUEUE_SIZE); // Shared data synchronized by call_queue_lock.

  int head = emscripten_atomic_load_u32((void*)&q->call_queue_head);
  int tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail);
  int new_tail = (tail + 1) % CALL_QUEUE_SIZE;

  while (new_tail == head) { // Queue is full?
    pthread_mutex_unlock(&call_queue_lock);

    // If queue of the main browser thread is full, then we wait. (never drop messages for the main
    // browser thread)
    if (target_thread == emscripten_main_browser_thread_id()) {
      emscripten_futex_wait((void*)&q->call_queue_head, head, INFINITY);
      pthread_mutex_lock(&call_queue_lock);
      head = emscripten_atomic_load_u32((void*)&q->call_queue_head);
      tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail);
      new_tail = (tail + 1) % CALL_QUEUE_SIZE;
    } else {
      // For the queues of other threads, just drop the message.
      // #if DEBUG TODO: a debug build of pthreads library?
      //			EM_ASM(console.error('Pthread queue overflowed, dropping queued
      //message to thread. ' + new Error().stack));
      // #endif
      em_queued_call_free(call);
      return 0;
    }
  }

  q->call_queue[tail] = call;

  // If the call queue was empty, the main runtime thread is likely idle in the browser event loop,
  // so send a message to it to ensure that it wakes up to start processing the command we have
  // posted.
  if (head == tail) {
    int success = _emscripten_notify_thread_queue(target_thread, emscripten_main_browser_thread_id());
    // Failed to dispatch the thread, delete the crafted message.
    if (!success) {
      em_queued_call_free(call);
      pthread_mutex_unlock(&call_queue_lock);
      return 0;
    }
  }

  emscripten_atomic_store_u32((void*)&q->call_queue_tail, new_tail);

  pthread_mutex_unlock(&call_queue_lock);

  return 0;
}

void EMSCRIPTEN_KEEPALIVE emscripten_async_run_in_main_thread(em_queued_call* call) {
  do_emscripten_dispatch_to_thread(emscripten_main_browser_thread_id(), call);
}

void EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread(em_queued_call* call) {
  emscripten_async_run_in_main_thread(call);

  // Enter to wait for the operation to complete.
  emscripten_wait_for_call_v(call, INFINITY);
}

void* EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_0(int function) {
  em_queued_call q = {function};
  q.returnValue.vp = 0;
  emscripten_sync_run_in_main_thread(&q);
  return q.returnValue.vp;
}

void* EMSCRIPTEN_KEEPALIVE emscripten_sync_run_in_main_thread_1(int function, void* arg1) {
  em_queued_call q = {function};
  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};
  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};
  q.args[0].vp = (void*)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};
  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};
  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};
  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};
  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};
  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;
}

void EMSCRIPTEN_KEEPALIVE emscripten_current_thread_process_queued_calls() {
  // #if PTHREADS_DEBUG == 2
  //	EM_ASM(console.error('thread ' + _pthread_self() + ':
  //emscripten_current_thread_process_queued_calls(), ' + new Error().stack));
  // #endif

  // TODO: Under certain conditions we may want to have a nesting guard also for pthreads (and it
  // will certainly be cleaner that way), but we don't yet have TLS variables outside
  // pthread_set/getspecific, so convert this to TLS after TLS is implemented.
  static int bool_main_thread_inside_nested_process_queued_calls = 0;

  if (emscripten_is_main_browser_thread()) {
    // 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_main_thread_inside_nested_process_queued_calls)
      return;
    // This must be before pthread_mutex_lock(), since pthread_mutex_lock() can call back to this
    // function.
    bool_main_thread_inside_nested_process_queued_calls = 1;
  }

  pthread_mutex_lock(&call_queue_lock);
  CallQueue* q = GetQueue(pthread_self());
  if (!q) {
    pthread_mutex_unlock(&call_queue_lock);
    if (emscripten_is_main_browser_thread())
      bool_main_thread_inside_nested_process_queued_calls = 0;
    return;
  }

  int head = emscripten_atomic_load_u32((void*)&q->call_queue_head);
  int tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail);
  while (head != tail) {
    // Assume that the call is heavy, so unlock access to the call queue while it is being
    // performed.
    pthread_mutex_unlock(&call_queue_lock);
    _do_call(q->call_queue[head]);
    pthread_mutex_lock(&call_queue_lock);

    head = (head + 1) % CALL_QUEUE_SIZE;
    emscripten_atomic_store_u32((void*)&q->call_queue_head, head);
    tail = emscripten_atomic_load_u32((void*)&q->call_queue_tail);
  }
  pthread_mutex_unlock(&call_queue_lock);

  // If the queue was full and we had waiters pending to get to put data to queue, wake them up.
  emscripten_futex_wake((void*)&q->call_queue_head, 0x7FFFFFFF);

  if (emscripten_is_main_browser_thread())
    bool_main_thread_inside_nested_process_queued_calls = 0;
}

void EMSCRIPTEN_KEEPALIVE emscripten_main_thread_process_queued_calls() {
  if (!emscripten_is_main_runtime_thread())
    return;

  emscripten_current_thread_process_queued_calls();
}

int emscripten_sync_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {
  int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig);
  em_queued_call q = {sig, func_ptr};

  EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK;
  va_list args;
  va_start(args, func_ptr);
  for (int i = 0; i < numArguments; ++i) {
    switch ((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) {
      case EM_FUNC_SIG_PARAM_I:
        q.args[i].i = va_arg(args, int);
        break;
      case EM_FUNC_SIG_PARAM_I64:
        q.args[i].i64 = va_arg(args, int64_t);
        break;
      case EM_FUNC_SIG_PARAM_F:
        q.args[i].f = (float)va_arg(args, double);
        break;
      case EM_FUNC_SIG_PARAM_D:
        q.args[i].d = va_arg(args, double);
        break;
    }
    argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT;
  }
  va_end(args);
  emscripten_sync_run_in_main_thread(&q);
  return q.returnValue.i;
}

EMSCRIPTEN_KEEPALIVE double emscripten_run_in_main_runtime_thread_js(int index, int num_args, double* buffer, int sync) {
  em_queued_call q;
  em_queued_call *c;
  if (sync) {
    q.operationDone = 0;
    q.satelliteData = 0;
    c = &q;
  } else {
    c = em_queued_call_malloc();
  }
  c->calleeDelete = 1-sync;
  c->functionEnum = EM_PROXIED_JS_FUNCTION;
  c->functionPtr = (void*)index;
  // We write out the JS doubles into args[], which must be of appropriate size - JS will assume that.
  assert(sizeof(em_variant_val) == sizeof(double));
  assert(num_args+1 <= EM_QUEUED_JS_CALL_MAX_ARGS);
  c->args[0].i = num_args;
  for (int i = 0; i < num_args; i++) {
    c->args[i+1].d = buffer[i];
  }

  if (sync) {
    emscripten_sync_run_in_main_thread(&q);
    return q.returnValue.d;
  } else {
    // 'async' runs are fire and forget, where the caller detaches itself from the call object after
    // returning here, and it is the callee's responsibility to free up the memory after the call
    // has been performed.
    emscripten_async_run_in_main_thread(c);
    return 0;
  }
}

void emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {
  int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig);
  em_queued_call* q = em_queued_call_malloc();
  if (!q)
    return;
  q->functionEnum = sig;
  q->functionPtr = func_ptr;

  EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK;
  va_list args;
  va_start(args, func_ptr);
  for (int i = 0; i < numArguments; ++i) {
    switch ((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) {
      case EM_FUNC_SIG_PARAM_I:
        q->args[i].i = va_arg(args, int);
        break;
      case EM_FUNC_SIG_PARAM_I64:
        q->args[i].i64 = va_arg(args, int64_t);
        break;
      case EM_FUNC_SIG_PARAM_F:
        q->args[i].f = (float)va_arg(args, double);
        break;
      case EM_FUNC_SIG_PARAM_D:
        q->args[i].d = va_arg(args, double);
        break;
    }
    argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT;
  }
  va_end(args);
  // 'async' runs are fire and forget, where the caller detaches itself from the call object after
  // returning here, and it is the callee's responsibility to free up the memory after the call has
  // been performed.
  q->calleeDelete = 1;
  emscripten_async_run_in_main_thread(q);
}

em_queued_call* emscripten_async_waitable_run_in_main_runtime_thread_(
  EM_FUNC_SIGNATURE sig, void* func_ptr, ...) {
  int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig);
  em_queued_call* q = em_queued_call_malloc();
  if (!q)
    return NULL;
  q->functionEnum = sig;
  q->functionPtr = func_ptr;

  EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK;
  va_list args;
  va_start(args, func_ptr);
  for (int i = 0; i < numArguments; ++i) {
    switch ((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) {
      case EM_FUNC_SIG_PARAM_I:
        q->args[i].i = va_arg(args, int);
        break;
      case EM_FUNC_SIG_PARAM_I64:
        q->args[i].i64 = va_arg(args, int64_t);
        break;
      case EM_FUNC_SIG_PARAM_F:
        q->args[i].f = (float)va_arg(args, double);
        break;
      case EM_FUNC_SIG_PARAM_D:
        q->args[i].d = va_arg(args, double);
        break;
    }
    argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT;
  }
  va_end(args);
  // 'async waitable' runs are waited on by the caller, so the call object needs to remain alive for
  // the caller to access it after the operation is done. The caller is responsible in cleaning up
  // the object after done.
  q->calleeDelete = 0;
  emscripten_async_run_in_main_thread(q);
  return q;
}

int EMSCRIPTEN_KEEPALIVE _emscripten_call_on_thread(
  int forceAsync,
  pthread_t targetThread, EM_FUNC_SIGNATURE sig, void* func_ptr, void* satellite, ...) {
  int numArguments = EM_FUNC_SIG_NUM_FUNC_ARGUMENTS(sig);
  em_queued_call* q = em_queued_call_malloc();
  assert(q);
  // TODO: handle errors in a better way, this pattern appears in several places
  //       in this file. The current behavior makes the calling thread hang as
  //       it waits (for synchronous calls).
  // If we failed to allocate, return 0 which means we did not execute anything
  // (we also never will in that case).
  if (!q)
    return 0;
  q->functionEnum = sig;
  q->functionPtr = func_ptr;
  q->satelliteData = satellite;

  EM_FUNC_SIGNATURE argumentsType = sig & EM_FUNC_SIG_ARGUMENTS_TYPE_MASK;
  va_list args;
  va_start(args, satellite);
  for (int i = 0; i < numArguments; ++i) {
    switch ((argumentsType & EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_MASK)) {
      case EM_FUNC_SIG_PARAM_I:
        q->args[i].i = va_arg(args, int);
        break;
      case EM_FUNC_SIG_PARAM_I64:
        q->args[i].i64 = va_arg(args, int64_t);
        break;
      case EM_FUNC_SIG_PARAM_F:
        q->args[i].f = (float)va_arg(args, double);
        break;
      case EM_FUNC_SIG_PARAM_D:
        q->args[i].d = va_arg(args, double);
        break;
    }
    argumentsType >>= EM_FUNC_SIG_ARGUMENT_TYPE_SIZE_SHIFT;
  }
  va_end(args);

  // 'async' runs are fire and forget, where the caller detaches itself from the call object after
  // returning here, and it is the callee's responsibility to free up the memory after the call has
  // been performed.
  // Note that the call here might not be async if on the same thread, but for
  // consistency use the same convention of calleeDelete.
  q->calleeDelete = 1;
  // The called function will not be async if we are on the same thread; force
  // async if the user asked for that.
  if (forceAsync) {
    EM_ASM({
      setTimeout(function() {
        _do_emscripten_dispatch_to_thread($0, $1);
      }, 0);
    }, targetThread, q);
    return 0;
  } else {
    return do_emscripten_dispatch_to_thread(targetThread, q);
  }
}

void llvm_memory_barrier() { emscripten_atomic_fence(); }

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

// Stores the memory address that the main thread is waiting on, if any. If
// the main thread is waiting, we wake it up before waking up any workers.
EMSCRIPTEN_KEEPALIVE
void* main_thread_futex;

typedef struct main_args {
  int argc;
  char** argv;
} main_args;

extern int __call_main(int argc, char** argv);

void* __emscripten_thread_main(void* param) {
  emscripten_set_thread_name(pthread_self(),
    "Application main thread"); // This is the main runtime thread for the application.
  main_args* args = (main_args*)param;
  return (void*)__call_main(args->argc, args->argv);
}

static main_args _main_arguments;

int proxy_main(int argc, char** argv) {
  if (emscripten_has_threading_support()) {
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    // Use the size of the current stack, which is the normal size of the stack
    // that main() would have without PROXY_TO_PTHREAD.
    pthread_attr_setstacksize(&attr, emscripten_stack_get_base() - emscripten_stack_get_end());
    // Pass special ID -1 to the list of transferred canvases to denote that the thread creation
    // should instead take a list of canvases that are specified from the command line with
    // -s OFFSCREENCANVASES_TO_PTHREAD linker flag.
    emscripten_pthread_attr_settransferredcanvases(&attr, (const char*)-1);
    _main_arguments.argc = argc;
    _main_arguments.argv = argv;
    pthread_t thread;
    int rc = pthread_create(&thread, &attr, __emscripten_thread_main, (void*)&_main_arguments);
    pthread_attr_destroy(&attr);
    if (rc) {
      // Proceed by running main() on the main browser thread as a fallback.
      return __call_main(_main_arguments.argc, _main_arguments.argv);
    }
    return 0;
  } else {
    return __call_main(_main_arguments.argc, _main_arguments.argv);
  }
}

weak_alias(__pthread_testcancel, pthread_testcancel);
