/*
 * Copyright 2021 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.
 */

#include "pthread_impl.h"
#include "stdio_impl.h"
#include "assert.h"
#include <pthread.h>
#include <stdbool.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>

#include <emscripten/heap.h>
#include <emscripten/threading.h>

#define STACK_ALIGN __BIGGEST_ALIGNMENT__
#define TSD_ALIGN (sizeof(void*))

// Comment this line to enable tracing of thread creation and destruction:
// #define PTHREAD_DEBUG
#ifdef PTHREAD_DEBUG
#define dbg(fmt, ...) emscripten_dbgf(fmt, ##__VA_ARGS__)
#else
#define dbg(fmt, ...)
#endif

// See musl's pthread_create.c

static void dummy_0() {}
weak_alias(dummy_0, __pthread_tsd_run_dtors);
weak_alias(dummy_0, __do_orphaned_stdio_locks);
weak_alias(dummy_0, __dl_thread_cleanup);

static void __run_cleanup_handlers() {
  pthread_t self = __pthread_self();
  while (self->cancelbuf) {
    void (*f)(void *) = self->cancelbuf->__f;
    void *x = self->cancelbuf->__x;
    self->cancelbuf = self->cancelbuf->__next;
    f(x);
  }
}

void __do_cleanup_push(struct __ptcb *cb) {
  struct pthread *self = __pthread_self();
  cb->__next = self->cancelbuf;
  self->cancelbuf = cb;
}

void __do_cleanup_pop(struct __ptcb *cb) {
  __pthread_self()->cancelbuf = cb->__next;
}

static FILE *volatile dummy_file = 0;
weak_alias(dummy_file, __stdin_used);
weak_alias(dummy_file, __stdout_used);
weak_alias(dummy_file, __stderr_used);

static void init_file_lock(FILE *f) {
  if (f && f->lock<0) f->lock = 0;
}

static int tl_lock_count;
static int tl_lock_waiters;

volatile int __thread_list_lock;

void __tl_lock(void) {
  int tid = __pthread_self()->tid;
  int val = __thread_list_lock;
  if (val == tid) {
    tl_lock_count++;
    return;
  }
  while ((val = a_cas(&__thread_list_lock, 0, tid)))
    __wait(&__thread_list_lock, &tl_lock_waiters, val, 0);
}

void __tl_unlock(void) {
  if (tl_lock_count) {
    tl_lock_count--;
    return;
  }
  a_store(&__thread_list_lock, 0);
  if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0);
}

void __tl_sync(pthread_t td)
{
	a_barrier();
	int val = __thread_list_lock;
	if (!val) return;
	__wait(&__thread_list_lock, &tl_lock_waiters, val, 0);
	if (tl_lock_waiters) __wake(&__thread_list_lock, 1, 0);
}

/* pthread_key_create.c overrides this */
static volatile size_t dummy = 0;
weak_alias(dummy, __pthread_tsd_size);

#define ROUND_UP(x, ALIGNMENT) (((x)+ALIGNMENT-1)&-ALIGNMENT)

int __pthread_create(pthread_t* restrict res,
                     const pthread_attr_t* restrict attrp,
                     void* (*entry)(void*),
                     void* restrict arg) {
  // Note on LSAN: lsan intercepts/wraps calls to pthread_create so any
  // allocations we do here should not be considered leaks.
  // See: lsan_interceptors.cpp.
  if (!res) {
    return EINVAL;
  }

  if (!libc.threaded) {
    for (FILE *f=*__ofl_lock(); f; f=f->next)
      init_file_lock(f);
    __ofl_unlock();
    init_file_lock(__stdin_used);
    init_file_lock(__stdout_used);
    init_file_lock(__stderr_used);
    libc.threaded = 1;
  }

  int c11 = (attrp == __ATTRP_C11_THREAD);
  pthread_attr_t attr = { 0 };
  if (attrp && !c11) attr = *attrp;
  if (!attrp || c11) {
    attr._a_stacksize = __default_stacksize;
    attr._a_guardsize = __default_guardsize;
  }

  size_t guard_size = 0;

  // Allocate memory for new thread.  The layout of the thread block is
  // as follows.  From low to high address:
  //
  // 1. pthread struct (sizeof struct pthread)
  // 2. tls data       (__builtin_wasm_tls_size())
  // 3. tsd pointers   (__pthread_tsd_size)
  // 4. guard area     (guard_size)
  // 5. stack          (__default_stacksize AKA -sDEFAULT_PTHREAD_STACK_SIZE)
  size_t size = sizeof(struct pthread);
  if (__builtin_wasm_tls_size()) {
    size += __builtin_wasm_tls_size() + __builtin_wasm_tls_align() - 1;
  }
  size += __pthread_tsd_size + TSD_ALIGN - 1;
  size_t zero_size = size;
  if (!attr._a_stackaddr) {
    guard_size = attr._a_guardsize;
    size += guard_size + attr._a_stacksize + STACK_ALIGN - 1;
    zero_size += guard_size;
  }

  // Allocate all the data for the new thread and zero-initialize all parts
  // except for the stack.
  unsigned char* block = emscripten_builtin_malloc(size);
  memset(block, 0, zero_size);

  uintptr_t offset = (uintptr_t)block;

  // 1. pthread struct
  struct pthread *new = (struct pthread*)offset;
  offset += sizeof(struct pthread);

  new->map_base = block;
  new->map_size = size;

  // The pthread struct has a field that points to itself - this is used as a
  // magic ID to detect whether the pthread_t structure is 'alive'.
  new->self = new;
  new->tid = _emscripten_get_next_tid();

  // pthread struct robust_list head should point to itself.
  new->robust_list.head = &new->robust_list.head;

  if (attr._a_detach) {
    new->detach_state = DT_DETACHED;
  } else {
    new->detach_state = DT_JOINABLE;
  }
  new->stack_size = attr._a_stacksize;
  new->guard_size = guard_size;

  // 2. tls data
  if (__builtin_wasm_tls_size()) {
    offset = ROUND_UP(offset, __builtin_wasm_tls_align());
    new->tls_base = (void*)offset;
    offset += __builtin_wasm_tls_size();
  }

  // 3. tsd slots
  if (__pthread_tsd_size) {
    offset = ROUND_UP(offset, TSD_ALIGN);
    new->tsd = (void*)offset;
    offset += __pthread_tsd_size;
  }

  // 4. stack data
  // musl stores top of the stack in pthread_t->stack (i.e. the high
  // end from which it grows down).
  if (attr._a_stackaddr) {
    new->stack = (void*)attr._a_stackaddr;
  } else {
    offset += guard_size;
    offset = ROUND_UP(offset + new->stack_size, STACK_ALIGN);
    new->stack = (void*)offset;
  }

  // Check that we didn't use more data than we allocated.
  assert(offset <= (uintptr_t)block + size);

#ifndef NDEBUG
  _emscripten_thread_profiler_init(new);
#endif

  _emscripten_thread_mailbox_init(new);

  struct pthread *self = __pthread_self();
  dbg("start __pthread_create: new=%p new_end=%p stack=%p->%p "
      "stack_size=%zu tls_base=%p",
      new,
      new + 1,
      (char*)new->stack - new->stack_size,
      new->stack,
      new->stack_size,
      new->tls_base);

  // thread may already be running/exited after the _pthread_create_js call below
  __tl_lock();

  new->next = self->next;
  new->prev = self;
  new->next->prev = new;
  new->prev->next = new;

  __tl_unlock();

  // Set libc.need_locks before calling __pthread_create_js since
  // by the time it returns the thread could be running and we
  // want libc.need_locks to be set from the moment it starts.
  if (!libc.threads_minus_1++) libc.need_locks = 1;

  // Assign the pthread_t object over immediately, so that by the time pthread_create_js()
  // is dispatched to a pthread and the pthread main runs, the value will be visible to
  // the thread to examine.
  // Use __atomic_store_n() instead of a_store() to avoid splicing the pointer.
  __atomic_store_n(res, new, __ATOMIC_SEQ_CST);

  int rtn = __pthread_create_js(new, &attr, entry, arg);
  if (rtn != 0) {
    // Reset the pthread_t return value to zero (we assigned to it above,
    // so by clearing it here we won't litter bits to caller)
    __atomic_store_n(res, 0, __ATOMIC_SEQ_CST);

    if (!--libc.threads_minus_1) libc.need_locks = 0;

    // undo previous addition to the thread list
    __tl_lock();

    new->next->prev = new->prev;
    new->prev->next = new->next;
    new->next = new->prev = new;

    __tl_unlock();

    return rtn;
  }

  dbg("done __pthread_create next=%p prev=%p new=%p",
      self->next,
      self->prev,
      new);

  return 0;
}

/*
 * Called from JS main thread to free data associated with a thread
 * that is no longer running.
 */
void _emscripten_thread_free_data(pthread_t t) {
  // A thread can never free its own thread data.
  assert(t != pthread_self());
#ifndef NDEBUG
  if (t->profilerBlock) {
    emscripten_builtin_free(t->profilerBlock);
  }
#endif
  // Clear the self-reference to prevent inadvertent use and
  // inform functions that validate it that the thread is no
  // longer available.
  t->self = NULL;

  // Free the entire thread block (called map_base because
  // musl normally allocates this using mmap).  This region
  // includes the pthread structure itself.
  unsigned char* block = t->map_base;
  dbg("_emscripten_thread_free_data thread=%p map_base=%p", t, block);
#ifndef NDEBUG
  // To aid in debugging, set the entire region to zero.
  memset(block, 0, sizeof(struct pthread));
#endif
  emscripten_builtin_free(block);
}

void _emscripten_thread_exit(void* result) {
  pthread_t self = __pthread_self();
  assert(self);

  self->canceldisable = 1;
  self->cancelasync = 0;
  self->result = result;

  _emscripten_thread_mailbox_shutdown(self);

  // Run any handlers registered with pthread_cleanup_push
  __run_cleanup_handlers();

  // Call into the musl function that runs destructors of all thread-specific data.
  __pthread_tsd_run_dtors();

  // If this is the main runtime thread, don't proceed with
  // termination of the thread, but prepare for exit to call
  // atexit handlers.
  if (emscripten_is_main_runtime_thread()) {
    exit(0);
  }

  /* At this point we are committed to thread termination. */

  /* The thread list lock must be AS-safe. */
  __tl_lock();

  /* Process robust list in userspace to handle non-pshared mutexes
   * and the detached thread case where the robust list head will
   * be invalid when the kernel would process it. */
  __vm_lock();
  volatile void *volatile *rp;
  while ((rp=self->robust_list.head) && rp != &self->robust_list.head) {
    pthread_mutex_t *m = (void *)((char *)rp
      - offsetof(pthread_mutex_t, _m_next));
    int waiters = m->_m_waiters;
    int priv = (m->_m_type & 128) ^ 128;
    self->robust_list.pending = rp;
    self->robust_list.head = *rp;
    int cont = a_swap(&m->_m_lock, 0x40000000);
    self->robust_list.pending = 0;
    if (cont < 0 || waiters)
      __wake(&m->_m_lock, 1, priv);
  }
  __vm_unlock();

  __do_orphaned_stdio_locks();
  __dl_thread_cleanup();

  /* Last, unlink thread from the list. This change will not be visible
   * until the lock is released via __tl_unlock() below. */
  if (!--libc.threads_minus_1) libc.need_locks = 0;
  self->next->prev = self->prev;
  self->prev->next = self->next;
  self->prev = self->next = self;

  __tl_unlock();

  // Not hosting a pthread anymore in this worker set __pthread_self to NULL
  __set_thread_state(NULL, 0, 0, 1);

  /* This atomic potentially competes with a concurrent pthread_detach
   * call; the loser is responsible for freeing thread resources. */
  int state = a_cas(&self->detach_state, DT_JOINABLE, DT_EXITING);

  if (state == DT_DETACHED) {
    _emscripten_thread_cleanup(self);
  } else {
    // Mark the thread as no longer running, so it can be joined.
    // Once we publish this, any threads that are waiting to join with us can
    // proceed and this worker can be recycled and used on another thread.
#ifdef EMSCRIPTEN_DYNAMIC_LINKING
    // When dynamic linking is enabled we need to keep track of zombie threads
    _emscripten_thread_exit_joinable(self);
#endif

    /* Wake any joiner. */
    a_store(&self->detach_state, DT_EXITED);
    __wake(&self->detach_state, 1, 1);
  }
}

// Mark as `no_sanitize("address")` since emscripten_pthread_exit destroys
// the current thread and runs its exit handlers.  Without this asan injects
// a call to __asan_handle_no_return before emscripten_unwind_to_js_event_loop
// which seem to cause a crash later down the line.
__attribute__((no_sanitize("address")))
_Noreturn void __pthread_exit(void* retval) {
  _emscripten_thread_exit(retval);
  emscripten_unwind_to_js_event_loop();
}

weak_alias(__pthread_create, emscripten_builtin_pthread_create);
weak_alias(__pthread_create, pthread_create);
weak_alias(__pthread_exit, emscripten_builtin_pthread_exit);
weak_alias(__pthread_exit, pthread_exit);
