/*
   Copyright (c) 2011 mingw-w64 project

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <windows.h>
#include <stdio.h>
#include <malloc.h>
#include <signal.h>
#include "pthread.h"
#include "thread.h"
#include "misc.h"
#include "winpthread_internal.h"

static _pthread_v *__pthread_self_lite (void);

void (**_pthread_key_dest)(void *) = NULL;

static volatile long _pthread_cancelling;
static int _pthread_concur;

/* FIXME Will default to zero as needed */
static pthread_once_t _pthread_tls_once;
static DWORD _pthread_tls = 0xffffffff;

static pthread_rwlock_t _pthread_key_lock = PTHREAD_RWLOCK_INITIALIZER;
static unsigned long _pthread_key_max=0L;
static unsigned long _pthread_key_sch=0L;

static _pthread_v *pthr_root = NULL, *pthr_last = NULL;
static pthread_mutex_t mtx_pthr_locked = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;

static __pthread_idlist *idList = NULL;
static size_t idListCnt = 0;
static size_t idListMax = 0;
static pthread_t idListNextId = 0;

/* Search the list idList for an element with identifier ID.  If
   found, its associated _pthread_v pointer is returned, otherwise
   NULL.
   NOTE: This method is not locked.  */
static struct _pthread_v *
__pthread_get_pointer (pthread_t id)
{
  size_t l, r, p;
  if (!idListCnt)
    return NULL;
  if (idListCnt == 1)
    return (idList[0].id == id ? idList[0].ptr : NULL);
  l = 0; r = idListCnt - 1;
  while (l <= r)
  {
    p = (l + r) >> 1;
    if (idList[p].id == id)
      return idList[p].ptr;
    else if (idList[p].id > id)
      {
	if (p == l)
	  return NULL;
	r = p - 1;
      }
    else
      {
	l = p + 1;
      }
  }

  return NULL;
}

static void
__pth_remove_use_for_key (pthread_key_t key)
{
  int i;

  pthread_mutex_lock (&mtx_pthr_locked);
  for (i = 0; i < idListCnt; i++)
    {
      if (idList[i].ptr != NULL
          && idList[i].ptr->keyval != NULL
          && key < idList[i].ptr->keymax)
        {
	  idList[i].ptr->keyval[key] = NULL;
	  idList[i].ptr->keyval_set[key] = 0;
	}
    }
  pthread_mutex_unlock (&mtx_pthr_locked);
}

/* Search the list idList for an element with identifier ID.  If
   found, its associated _pthread_v pointer is returned, otherwise
   NULL.
   NOTE: This method uses lock mtx_pthr_locked.  */
struct _pthread_v *
__pth_gpointer_locked (pthread_t id)
{
  struct _pthread_v *ret;
  if (!id)
    return NULL;
  pthread_mutex_lock (&mtx_pthr_locked);
  ret =  __pthread_get_pointer (id);
  pthread_mutex_unlock (&mtx_pthr_locked);
  return ret;
}

/* Registers in the list idList an element with _pthread_v pointer
   and creates and unique identifier ID.  If successful created the
   ID of this element is returned, otherwise on failure zero ID gets
   returned.
   NOTE: This method is not locked.  */
static pthread_t
__pthread_register_pointer (struct _pthread_v *ptr)
{
  __pthread_idlist *e;
  size_t i;

  if (!ptr)
    return 0;
  /* Check if a resize of list is necessary.  */
  if (idListCnt >= idListMax)
    {
      if (!idListCnt)
        {
	  e = (__pthread_idlist *) malloc (sizeof (__pthread_idlist) * 16);
	  if (!e)
	    return 0;
	  idListMax = 16;
	  idList = e;
	}
      else
        {
	  e = (__pthread_idlist *) realloc (idList, sizeof (__pthread_idlist) * (idListMax + 16));
	  if (!e)
	    return 0;
	  idListMax += 16;
	  idList = e;
	}
    }
  do
    {
      ++idListNextId;
      /* If two MSB are set we reset to id 1.  We need to check here bits
         to avoid gcc's no-overflow issue on increment.  Additionally we
         need to handle different size of pthread_t on 32-bit/64-bit.  */
      if ((idListNextId & ( ((pthread_t) 1) << ((sizeof (pthread_t) * 8) - 2))) != 0)
        idListNextId = 1;
    }
  while (idListNextId == 0 || __pthread_get_pointer (idListNextId));
  /* We assume insert at end of list.  */
  i = idListCnt;
  if (i != 0)
    {
      /* Find position we can actual insert sorted.  */
      while (i > 0 && idList[i - 1].id > idListNextId)
        --i;
      if (i != idListCnt)
	memmove (&idList[i + 1], &idList[i], sizeof (__pthread_idlist) * (idListCnt - i));
    }
  idList[i].id = idListNextId;
  idList[i].ptr = ptr;
  ++idListCnt;
  return idListNextId;
}

/* Deregisters in the list idList an element with identifier ID and
   returns its _pthread_v pointer on success.  Otherwise NULL is returned.
   NOTE: This method is not locked.  */
static struct _pthread_v *
__pthread_deregister_pointer (pthread_t id)
{
  size_t l, r, p;
  if (!idListCnt)
    return NULL;
  l = 0; r = idListCnt - 1;
  while (l <= r)
  {
    p = (l + r) >> 1;
    if (idList[p].id == id)
      {
	struct _pthread_v *ret = idList[p].ptr;
	p++;
	if (p < idListCnt)
	  memmove (&idList[p - 1], &idList[p], sizeof (__pthread_idlist) * (idListCnt - p));
	--idListCnt;
	/* Is this last element in list then free list.  */
	if (idListCnt == 0)
	{
	  free (idList);
	  idListCnt = idListMax = 0;
	}
	return ret;
      }
    else if (idList[p].id > id)
      {
	if (p == l)
	  return NULL;
	r = p - 1;
      }
    else
      {
	l = p + 1;
      }
  }
  return NULL;
}

/* Save a _pthread_v element for reuse in pool.  */
static void
push_pthread_mem (_pthread_v *sv)
{
  if (!sv || sv->next != NULL)
    return;
  pthread_mutex_lock (&mtx_pthr_locked);
  if (sv->x != 0)
    __pthread_deregister_pointer (sv->x);
  if (sv->keyval)
    free (sv->keyval);
  if (sv->keyval_set)
    free (sv->keyval_set);
  memset (sv, 0, sizeof(struct _pthread_v));
  if (pthr_last == NULL)
    pthr_root = pthr_last = sv;
  else
    pthr_last->next = sv;
  pthread_mutex_unlock (&mtx_pthr_locked);
}

/* Get a _pthread_v element from pool, or allocate it.
   Note the unique identifier is created for the element here, too.  */
static _pthread_v *
pop_pthread_mem (void)
{
  _pthread_v *r = NULL;

  pthread_mutex_lock (&mtx_pthr_locked);
  if ((r = pthr_root) == NULL)
    {
      if ((r = (_pthread_v *)calloc (1,sizeof(struct _pthread_v))) != NULL)
	{
	  r->x = __pthread_register_pointer (r);
	  if (r->x == 0)
	    {
	      free (r);
	      r = NULL;
	    }
	}
      pthread_mutex_unlock (&mtx_pthr_locked);
      return r;
    }
  r->x = __pthread_register_pointer (r);
  if (r->x == 0)
    r = NULL;
  else
    {
      if((pthr_root = r->next) == NULL)
	pthr_last = NULL;

      r->next = NULL;
    }
  pthread_mutex_unlock (&mtx_pthr_locked);
  return r;
}

/* Free memory consumed in _pthread_v pointer pool.  */
static void
free_pthread_mem (void)
{
  _pthread_v *t;

  if (1)
  return;  
  pthread_mutex_lock (&mtx_pthr_locked);
  t = pthr_root;
  while (t != NULL)
  {
    _pthread_v *sv = t;
    t = t->next;
    if (sv->x != 0 && sv->ended == 0 && sv->valid != DEAD_THREAD)
      {
	pthread_mutex_unlock (&mtx_pthr_locked);
	pthread_cancel (t->x);
	Sleep (0);
	pthread_mutex_lock (&mtx_pthr_locked);
	t = pthr_root;
	continue;
      }
    else if (sv->x != 0 && sv->valid != DEAD_THREAD)
      {
	pthread_mutex_unlock (&mtx_pthr_locked);
	Sleep (0);
	pthread_mutex_lock (&mtx_pthr_locked);
	continue;
      }
    if (sv->x != 0)
      __pthread_deregister_pointer (sv->x);
    sv->x = 0;
    free (sv);
    pthr_root = t;
  }
  pthread_mutex_unlock (&mtx_pthr_locked);
}

/* Hook for TLS-based deregistration/registration of thread.  */
static BOOL WINAPI
__dyn_tls_pthread (HANDLE hDllHandle, DWORD dwReason, LPVOID lpreserved)
{
  _pthread_v *t = NULL;
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  if (dwReason == DLL_PROCESS_DETACH)
    {
      free_pthread_mem ();
    }
  else if (dwReason == DLL_THREAD_DETACH)
    {
      if (_pthread_tls != 0xffffffff)
	t = (_pthread_v *)TlsGetValue(_pthread_tls);
      if (t && t->thread_noposix != 0)
	{
	  _pthread_cleanup_dest (t->x);
	  if (t->h != NULL)
	    {
	      CloseHandle (t->h);
	      if (t->evStart)
		CloseHandle (t->evStart);
	      t->evStart = NULL;
	      t->h = NULL;
	    }
	  pthread_mutex_destroy (&t->p_clock);
	  t->spin_keys = new_spin_keys;
	  push_pthread_mem (t);
	  t = NULL;
	  TlsSetValue (_pthread_tls, t);
	}
      else if (t && t->ended == 0)
	{
	  if (t->evStart)
	    CloseHandle(t->evStart);
	  t->evStart = NULL;
	  t->ended = 1;
	  _pthread_cleanup_dest (t->x);
	  if ((t->p_state & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)
	    {
	      t->valid = DEAD_THREAD;
	      if (t->h != NULL)
		CloseHandle (t->h);
	      t->h = NULL;
	      pthread_mutex_destroy(&t->p_clock);
	      t->spin_keys = new_spin_keys;
	      push_pthread_mem (t);
	      t = NULL;
	      TlsSetValue (_pthread_tls, t);
	      return TRUE;
	    }
	  pthread_mutex_destroy(&t->p_clock);
	  t->spin_keys = new_spin_keys;
	}
      else if (t)
	{
	  if (t->evStart)
	    CloseHandle (t->evStart);
	  t->evStart = NULL;
	  pthread_mutex_destroy (&t->p_clock);
	  t->spin_keys = new_spin_keys;
	}
    }
  return TRUE;
}

/* TLS-runtime section variable.  */
#ifdef _MSC_VER
#pragma section(".CRT$XLF", shared)
#endif
PIMAGE_TLS_CALLBACK WINPTHREADS_ATTRIBUTE((WINPTHREADS_SECTION(".CRT$XLF"))) __xl_f  = (PIMAGE_TLS_CALLBACK) __dyn_tls_pthread;
#ifdef _MSC_VER
#pragma data_seg()
#endif

#ifdef WINPTHREAD_DBG
static int print_state = 0;
void thread_print_set (int state)
{
  print_state = state;
}

void
thread_print (volatile pthread_t t, char *txt)
{
    if (!print_state)
      return;
    if (!t)
      printf("T%p %d %s\n",NULL,(int)GetCurrentThreadId(),txt);
    else
      {
	printf("T%p %d V=%0X H=%p %s\n",
	    __pth_gpointer_locked (t), 
	    (int)GetCurrentThreadId(), 
	    (int) (__pth_gpointer_locked (t))->valid, 
	    (__pth_gpointer_locked (t))->h,
	    txt
	    );
      }
}
#endif

/* Internal collect-once structure.  */
typedef struct collect_once_t {
  pthread_once_t *o;
  pthread_mutex_t m;
  int count;
  struct collect_once_t *next;
} collect_once_t;

static collect_once_t *once_obj = NULL;

static pthread_spinlock_t once_global = PTHREAD_SPINLOCK_INITIALIZER;

static collect_once_t *
enterOnceObject (pthread_once_t *o)
{
  collect_once_t *c, *p = NULL;
  pthread_spin_lock (&once_global);
  c = once_obj;
  while (c != NULL && c->o != o)
    {
      c = (p = c)->next;
    }
  if (!c)
    {
      c = (collect_once_t *) calloc(1,sizeof(collect_once_t));
      c->o = o;
      c->count = 1;
      if (!p)
        once_obj = c;
      else
        p->next = c;
      pthread_mutex_init(&c->m, NULL);
    }
  else
    c->count += 1;
  pthread_spin_unlock (&once_global);
  return c;
}

static void
leaveOnceObject (collect_once_t *c)
{
  collect_once_t *h, *p = NULL;
  if (!c)
    return;
  pthread_spin_lock (&once_global);
  h = once_obj;
  while (h != NULL && c != h)
    h = (p = h)->next;

  if (h)
    {
      c->count -= 1;
      if (c->count == 0)
	{
	  pthread_mutex_destroy(&c->m);
	  if (!p)
	    once_obj = c->next;
	  else
	    p->next = c->next;
	  free (c);
	}
    }
  else
    fprintf(stderr, "%p not found?!?!\n", c);
  pthread_spin_unlock (&once_global);
}

static void
_pthread_once_cleanup (void *o)
{
  collect_once_t *co = (collect_once_t *) o;
  pthread_mutex_unlock (&co->m);
  leaveOnceObject (co);
}

static int
_pthread_once_raw (pthread_once_t *o, void (*func)(void))
{
  collect_once_t *co;
  long state = *o;

  CHECK_PTR(o);
  CHECK_PTR(func);

  if (state == 1)
    return 0;
  co = enterOnceObject(o);
  pthread_mutex_lock(&co->m);
  if (*o == 0)
    {
      func();
      *o = 1;
    }
  else if (*o != 1)
    fprintf (stderr," once %p is %d\n", o, (int) *o);
  pthread_mutex_unlock(&co->m);
  leaveOnceObject(co);

  /* Done */
  return 0;
}

/* Unimplemented.  */
void *
pthread_timechange_handler_np(void *dummy)
{
  return NULL;
}

/* Compatibility routine for pthread-win32.  It waits for ellapse of
   interval and additionally checks for possible thread-cancelation.  */
int
pthread_delay_np (const struct timespec *interval)
{
  DWORD to = (!interval ? 0 : dwMilliSecs (_pthread_time_in_ms_from_timespec (interval)));
  struct _pthread_v *s = __pthread_self_lite ();

  if (!to)
    {
      pthread_testcancel ();
      Sleep (0);
      pthread_testcancel ();
      return 0;
    }
  pthread_testcancel ();
  if (s->evStart)
    WaitForSingleObject (s->evStart, to);
  else
    Sleep (to);
  pthread_testcancel ();
  return 0;
}

int pthread_delay_np_ms (DWORD to);

int
pthread_delay_np_ms (DWORD to)
{
  struct _pthread_v *s = __pthread_self_lite ();

  if (!to)
    {
      pthread_testcancel ();
      Sleep (0);
      pthread_testcancel ();
      return 0;
    }
  pthread_testcancel ();
  if (s->evStart)
    WaitForSingleObject (s->evStart, to);
  else
    Sleep (to);
  pthread_testcancel ();
  return 0;
}

/* Compatibility routine for pthread-win32.  It returns the
   amount of available CPUs on system.  */
int
pthread_num_processors_np(void) 
{
  int r = 0;
  DWORD_PTR ProcessAffinityMask, SystemAffinityMask;

  if (GetProcessAffinityMask(GetCurrentProcess(), &ProcessAffinityMask, &SystemAffinityMask))
    {
      for(; ProcessAffinityMask != 0; ProcessAffinityMask >>= 1)
	r += (ProcessAffinityMask & 1) != 0;
    }
  /* assume at least 1 */
  return r ? r : 1;
}

/* Compatiblity routine for pthread-win32.  Allows to set amount of used
   CPUs for process.  */
int
pthread_set_num_processors_np(int n) 
{
  DWORD_PTR ProcessAffinityMask, ProcessNewAffinityMask = 0, SystemAffinityMask;
  int r = 0; 
  /* need at least 1 */
  n = n ? n : 1;
  if (GetProcessAffinityMask (GetCurrentProcess (), &ProcessAffinityMask, &SystemAffinityMask))
    {
      for (; ProcessAffinityMask != 0; ProcessAffinityMask >>= 1)
	{
	  ProcessNewAffinityMask <<= 1;
	  if ((ProcessAffinityMask & 1) != 0 && r < n)
	    {
	      ProcessNewAffinityMask |= 1;
	      r++;
	    }
	}
      SetProcessAffinityMask (GetCurrentProcess (),ProcessNewAffinityMask);
    }
  return r;
}

int
pthread_once (pthread_once_t *o, void (*func)(void))
{
  collect_once_t *co;
  long state = *o;

  CHECK_PTR(o);
  CHECK_PTR(func);

  if (state == 1)
    return 0;
  co = enterOnceObject(o);
  pthread_mutex_lock(&co->m);
  if (*o == 0)
    {
      pthread_cleanup_push(_pthread_once_cleanup, co);
      func();
      pthread_cleanup_pop(0);
      *o = 1;
    }
  else if (*o != 1)
    fprintf (stderr," once %p is %d\n", o, (int) *o);
  pthread_mutex_unlock(&co->m);
  leaveOnceObject(co);

  return 0;
}

int
pthread_key_create (pthread_key_t *key, void (* dest)(void *))
{
	unsigned int i;
	long nmax;
	void (**d)(void *);

	if (!key)
		return EINVAL;

	pthread_rwlock_wrlock (&_pthread_key_lock);

	for (i = _pthread_key_sch; i < _pthread_key_max; i++)
	{
		if (!_pthread_key_dest[i])
		{
			*key = i;
			if (dest)
				_pthread_key_dest[i] = dest;
			else
				_pthread_key_dest[i] = (void(*)(void *))1;
			pthread_rwlock_unlock (&_pthread_key_lock);
			return 0;
		}
	}

	for (i = 0; i < _pthread_key_sch; i++)
	{
		if (!_pthread_key_dest[i])
		{
			*key = i;
			if (dest)
				_pthread_key_dest[i] = dest;
			else
				_pthread_key_dest[i] = (void(*)(void *))1;
			pthread_rwlock_unlock (&_pthread_key_lock);

			return 0;
		}
	}

	if (_pthread_key_max == PTHREAD_KEYS_MAX)
	{
		pthread_rwlock_unlock(&_pthread_key_lock);
		return ENOMEM;
	}

	nmax = _pthread_key_max * 2;
	if (nmax == 0)
		nmax = _pthread_key_max + 1;
	if (nmax > PTHREAD_KEYS_MAX)
		nmax = PTHREAD_KEYS_MAX;

	/* No spare room anywhere */
	d = (void (__cdecl **)(void *))realloc(_pthread_key_dest, nmax * sizeof(*d));
	if (!d)
	{
		pthread_rwlock_unlock (&_pthread_key_lock);
		return ENOMEM;
	}

	/* Clear new region */
	memset ((void *) &d[_pthread_key_max], 0, (nmax-_pthread_key_max)*sizeof(void *));

	/* Use new region */
	_pthread_key_dest = d;
	_pthread_key_sch = _pthread_key_max + 1;
	*key = _pthread_key_max;
	_pthread_key_max = nmax;

	if (dest)
		_pthread_key_dest[*key] = dest;
	else
		_pthread_key_dest[*key] = (void(*)(void *))1;

	pthread_rwlock_unlock (&_pthread_key_lock);
	return 0;
}

int
pthread_key_delete (pthread_key_t key)
{
  if (key >= _pthread_key_max || !_pthread_key_dest)
    return EINVAL;

  pthread_rwlock_wrlock (&_pthread_key_lock);
  
  _pthread_key_dest[key] = NULL;

  /* Start next search from our location */
  if (_pthread_key_sch > key)
    _pthread_key_sch = key;
  /* So now we need to walk the complete list of threads
     and remove key's reference for it.  */
  __pth_remove_use_for_key (key);

  pthread_rwlock_unlock (&_pthread_key_lock);
  return 0;
}

void *
pthread_getspecific (pthread_key_t key)
{
  DWORD lasterr = GetLastError ();
  void *r;
  _pthread_v *t = __pthread_self_lite ();
  pthread_spin_lock (&t->spin_keys);
  r = (key >= t->keymax || t->keyval_set[key] == 0 ? NULL : t->keyval[key]);
  pthread_spin_unlock (&t->spin_keys);
  SetLastError (lasterr);
  return r;
}

int
pthread_setspecific (pthread_key_t key, const void *value)
{
  DWORD lasterr = GetLastError ();
  _pthread_v *t = __pthread_self_lite ();
  
  pthread_spin_lock (&t->spin_keys);

  if (key >= t->keymax)
    {
      int keymax = (key + 1);
      void **kv;
      unsigned char *kv_set;

      kv = (void **) realloc (t->keyval, keymax * sizeof (void *));

      if (!kv)
        {
	  pthread_spin_unlock (&t->spin_keys);
	  return ENOMEM;
	}
      kv_set = (unsigned char *) realloc (t->keyval_set, keymax);
      if (!kv_set)
        {
	  pthread_spin_unlock (&t->spin_keys);
	  return ENOMEM;
	}

      /* Clear new region */
      memset (&kv[t->keymax], 0, (keymax - t->keymax)*sizeof(void *));
      memset (&kv_set[t->keymax], 0, (keymax - t->keymax));

      t->keyval = kv;
      t->keyval_set = kv_set;
      t->keymax = keymax;
    }

  t->keyval[key] = (void *) value;
  t->keyval_set[key] = 1;
  pthread_spin_unlock (&t->spin_keys);
  SetLastError (lasterr);

  return 0;
}

int
pthread_equal (pthread_t t1, pthread_t t2)
{
  return (t1 == t2);
}

void
pthread_tls_init (void)
{
  _pthread_tls = TlsAlloc();

  /* Cannot continue if out of indexes */
  if (_pthread_tls == TLS_OUT_OF_INDEXES)
    abort();
}

void
_pthread_cleanup_dest (pthread_t t)
{
	_pthread_v *tv;
	unsigned int i, j;

	if (!t)
		return;
	tv = __pth_gpointer_locked (t);
	if (!tv)
		return;

	for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++)
	{
		int flag = 0;

		pthread_spin_lock (&tv->spin_keys);
		for (i = 0; i < tv->keymax; i++)
		{
			void *val = tv->keyval[i];

			if (tv->keyval_set[i])
			{
				pthread_rwlock_rdlock (&_pthread_key_lock);
				if ((uintptr_t) _pthread_key_dest[i] > 1)
				{
					/* Call destructor */
					tv->keyval[i] = NULL;
					tv->keyval_set[i] = 0;
					pthread_spin_unlock (&tv->spin_keys);
					_pthread_key_dest[i](val);
					pthread_spin_lock (&tv->spin_keys);
					flag = 1;
				}
				else
				{
					tv->keyval[i] = NULL;
					tv->keyval_set[i] = 0;
				}
				pthread_rwlock_unlock(&_pthread_key_lock);
			}
		}
		pthread_spin_unlock (&tv->spin_keys);
		/* Nothing to do? */
		if (!flag)
			return;
	}
}

static _pthread_v *
__pthread_self_lite (void)
{
  _pthread_v *t;
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  _pthread_once_raw (&_pthread_tls_once, pthread_tls_init);

  t = (_pthread_v *) TlsGetValue (_pthread_tls);
  if (t)
    return t;
  /* Main thread? */
  t = (struct _pthread_v *) pop_pthread_mem ();

  /* If cannot initialize main thread, then the only thing we can do is return null pthread_t */
  if (!__xl_f || !t)
    return 0;

  t->p_state = PTHREAD_DEFAULT_ATTR /*| PTHREAD_CREATE_DETACHED*/;
  t->tid = GetCurrentThreadId();
  t->evStart = CreateEvent (NULL, 1, 0, NULL);
  t->p_clock = PTHREAD_MUTEX_INITIALIZER;
  t->spin_keys = new_spin_keys;
  t->sched_pol = SCHED_OTHER;
  t->h = NULL; //GetCurrentThread();
  if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &t->h, 0, FALSE, DUPLICATE_SAME_ACCESS))
    abort ();
  t->sched.sched_priority = GetThreadPriority(t->h);
  t->ended = 0;
  t->thread_noposix = 1;

  /* Save for later */
  if (!TlsSetValue(_pthread_tls, t))
    abort ();
  return t;
}

pthread_t
pthread_self (void)
{
  _pthread_v *t = __pthread_self_lite ();

  if (!t)
    return 0;
  return t->x;
}

/* Internal helper for getting event handle of thread T.  */
void *
pthread_getevent ()
{
  _pthread_v *t = __pthread_self_lite ();
  return (!t ? NULL : t->evStart);
}

/* Internal helper for getting thread handle of thread T.  */
void *
pthread_gethandle (pthread_t t)
{
  struct _pthread_v *tv = __pth_gpointer_locked (t);
  return (!tv ? NULL : tv->h);
}

/* Internal helper for getting pointer of clean of current thread.  */
struct _pthread_cleanup **
pthread_getclean (void)
{
  struct _pthread_v *t = __pthread_self_lite ();
  if (!t) return NULL;
  return &t->clean;
}

int
pthread_get_concurrency (int *val)
{
  *val = _pthread_concur;
  return 0;
}

int
pthread_set_concurrency (int val)
{
  _pthread_concur = val;
  return 0;
}

void
pthread_exit (void *res)
{
  _pthread_v *t = NULL;
  unsigned rslt = (unsigned) ((intptr_t) res);
  struct _pthread_v *id = __pthread_self_lite ();

  id->ret_arg = res;

  _pthread_cleanup_dest (id->x);
  if (id->thread_noposix == 0)
    longjmp(id->jb, 1);

  /* Make sure we free ourselves if we are detached */
  if ((t = (_pthread_v *)TlsGetValue(_pthread_tls)) != NULL)
    {
      if (!t->h)
	{
	  t->valid = DEAD_THREAD;
	  if (t->evStart)
	    CloseHandle (t->evStart);
	  t->evStart = NULL;
	  rslt = (unsigned) (size_t) t->ret_arg;
	  push_pthread_mem(t);
	  t = NULL;
	  TlsSetValue (_pthread_tls, t);
	}
      else
	{
	  rslt = (unsigned) (size_t) t->ret_arg;
	  t->ended = 1;
	  if (t->evStart)
	    CloseHandle (t->evStart);
	  t->evStart = NULL;
	  if ((t->p_state & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)
	    {
	      t->valid = DEAD_THREAD;
	      CloseHandle (t->h);
	      t->h = NULL;
	      push_pthread_mem(t);
	      t = NULL;
	      TlsSetValue(_pthread_tls, t);
	    }
	}
    }
  /* Time to die */
  _endthreadex(rslt);
}

void
_pthread_invoke_cancel (void)
{
  _pthread_cleanup *pcup;
  struct _pthread_v *se = __pthread_self_lite ();
  se->in_cancel = 1;
  _pthread_setnobreak (1);
  InterlockedDecrement(&_pthread_cancelling);

  /* Call cancel queue */
  for (pcup = se->clean; pcup; pcup = pcup->next)
    {
      pcup->func((pthread_once_t *)pcup->arg);
    }

  _pthread_setnobreak (0);
  pthread_exit(PTHREAD_CANCELED);
}

int
__pthread_shallcancel (void)
{
  struct _pthread_v *t;
  if (!_pthread_cancelling)
    return 0;
  t = __pthread_self_lite ();
  if (t == NULL)
    return 0;
  if (t->nobreak <= 0 && t->cancelled && (t->p_state & PTHREAD_CANCEL_ENABLE))
    return 1;
  return 0;
}

void
_pthread_setnobreak (int v)
{
  struct _pthread_v *t = __pthread_self_lite ();
  if (t == NULL)
    return;
  if (v > 0)
    InterlockedIncrement ((long*)&t->nobreak);
  else
    InterlockedDecrement((long*)&t->nobreak);
}

void
pthread_testcancel (void)
{
  struct _pthread_v *self = __pthread_self_lite ();

  if (!self || self->in_cancel)
    return;
  if (!_pthread_cancelling)
    return;
  pthread_mutex_lock (&self->p_clock);

  if (self->cancelled && (self->p_state & PTHREAD_CANCEL_ENABLE) && self->nobreak <= 0)
    {
      self->in_cancel = 1;
      self->p_state &= ~PTHREAD_CANCEL_ENABLE;
      if (self->evStart)
	ResetEvent (self->evStart);
      pthread_mutex_unlock (&self->p_clock);
      _pthread_invoke_cancel ();
    }
  pthread_mutex_unlock (&self->p_clock);
}

int
pthread_cancel (pthread_t t)
{
  struct _pthread_v *tv = __pth_gpointer_locked (t);

  if (tv == NULL)
    return ESRCH;
  CHECK_OBJECT(tv, ESRCH);
  /*if (tv->ended) return ESRCH;*/
  pthread_mutex_lock(&tv->p_clock);
  if (pthread_equal(pthread_self(), t))
    {
      if(tv->cancelled)
	{
	  pthread_mutex_unlock(&tv->p_clock);
	  return (tv->in_cancel ? ESRCH : 0);
	}
      tv->cancelled = 1;
      InterlockedIncrement(&_pthread_cancelling);
      if(tv->evStart) SetEvent(tv->evStart);
      if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) != 0 && (tv->p_state & PTHREAD_CANCEL_ENABLE) != 0)
	{
	  tv->p_state &= ~PTHREAD_CANCEL_ENABLE;
	  tv->in_cancel = 1;
	  pthread_mutex_unlock(&tv->p_clock);
	  _pthread_invoke_cancel();
	}
      else
	pthread_mutex_unlock(&tv->p_clock);
      return 0;
    }

  if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) != 0 && (tv->p_state & PTHREAD_CANCEL_ENABLE) != 0)
    {
      /* Dangerous asynchronous cancelling */
      CONTEXT ctxt;

      if(tv->in_cancel)
	{
	  pthread_mutex_unlock(&tv->p_clock);
	  return (tv->in_cancel ? ESRCH : 0);
	}
      /* Already done? */
      if(tv->cancelled || tv->in_cancel)
	{
	  /* ??? pthread_mutex_unlock (&tv->p_clock); */
	  return ESRCH;
	}

      ctxt.ContextFlags = CONTEXT_CONTROL;

      SuspendThread (tv->h);
      if (WaitForSingleObject (tv->h, 0) == WAIT_TIMEOUT)
	{
	  GetThreadContext(tv->h, &ctxt);
#ifdef _M_X64
	  ctxt.Rip = (uintptr_t) _pthread_invoke_cancel;
#else
	  ctxt.Eip = (uintptr_t) _pthread_invoke_cancel;
#endif
	  SetThreadContext (tv->h, &ctxt);

	  /* Also try deferred Cancelling */
	  tv->cancelled = 1;
	  tv->p_state &= ~PTHREAD_CANCEL_ENABLE;
	  tv->in_cancel = 1;

	  /* Notify everyone to look */
	  InterlockedIncrement (&_pthread_cancelling);
	  if (tv->evStart)
	    SetEvent (tv->evStart);
	  pthread_mutex_unlock (&tv->p_clock);

	  ResumeThread (tv->h);
	}
    }
  else
    {
      if (tv->cancelled == 0)
	{
	  /* Safe deferred Cancelling */
	  tv->cancelled = 1;

	  /* Notify everyone to look */
	  InterlockedIncrement (&_pthread_cancelling);
	  if (tv->evStart)
	    SetEvent (tv->evStart);
	}
      else
	{
	  pthread_mutex_unlock (&tv->p_clock);
	  return (tv->in_cancel ? ESRCH : 0);
	}
    }
  pthread_mutex_unlock (&tv->p_clock);
  return 0;
}

/* half-stubbed version as we don't really well support signals */
int
pthread_kill (pthread_t t, int sig)
{
  struct _pthread_v *tv;

  pthread_mutex_lock (&mtx_pthr_locked);
  tv = __pthread_get_pointer (t);
  if (!tv || t != tv->x || tv->in_cancel || tv->ended || tv->h == NULL
      || tv->h == INVALID_HANDLE_VALUE)
  {
    pthread_mutex_unlock (&mtx_pthr_locked);
    return ESRCH;
  }
  pthread_mutex_unlock (&mtx_pthr_locked);
  if (!sig)
    return 0;
  if (sig < SIGINT || sig > NSIG)
    return EINVAL;
  return pthread_cancel(t);
}

unsigned
_pthread_get_state (const pthread_attr_t *attr, unsigned flag)
{
  return (attr->p_state & flag);
}

int
_pthread_set_state (pthread_attr_t *attr, unsigned flag, unsigned val)
{
  if (~flag & val)
    return EINVAL;
  attr->p_state &= ~flag;
  attr->p_state |= val;

  return 0;
}

int
pthread_attr_init (pthread_attr_t *attr)
{
  memset (attr, 0, sizeof (pthread_attr_t));
  attr->p_state = PTHREAD_DEFAULT_ATTR;
  attr->stack = NULL;
  attr->s_size = 0;
  return 0;
}

int
pthread_attr_destroy (pthread_attr_t *attr)
{
  /* No need to do anything */
  memset (attr, 0, sizeof(pthread_attr_t));
  return 0;
}

int
pthread_attr_setdetachstate (pthread_attr_t *a, int flag)
{
  return _pthread_set_state(a, PTHREAD_CREATE_DETACHED, flag);
}

int
pthread_attr_getdetachstate (const pthread_attr_t *a, int *flag)
{
  *flag = _pthread_get_state(a, PTHREAD_CREATE_DETACHED);
  return 0;
}

int
pthread_attr_setinheritsched (pthread_attr_t *a, int flag)
{
  if (!a || (flag != PTHREAD_INHERIT_SCHED && flag != PTHREAD_EXPLICIT_SCHED))
    return EINVAL;
  return _pthread_set_state(a, PTHREAD_INHERIT_SCHED, flag);
}

int
pthread_attr_getinheritsched (const pthread_attr_t *a, int *flag)
{
  *flag = _pthread_get_state(a, PTHREAD_INHERIT_SCHED);
  return 0;
}

int
pthread_attr_setscope (pthread_attr_t *a, int flag)
{
  return _pthread_set_state(a, PTHREAD_SCOPE_SYSTEM, flag);
}

int
pthread_attr_getscope (const pthread_attr_t *a, int *flag)
{
  *flag = _pthread_get_state(a, PTHREAD_SCOPE_SYSTEM);
  return 0;
}

int
pthread_attr_getstackaddr (pthread_attr_t *attr, void **stack)
{
  *stack = attr->stack;
  return 0;
}

int
pthread_attr_setstackaddr (pthread_attr_t *attr, void *stack)
{
  attr->stack = stack;
  return 0;
}

int
pthread_attr_getstacksize (const pthread_attr_t *attr, size_t *size)
{
  *size = attr->s_size;
  return 0;
}

int
pthread_attr_setstacksize (pthread_attr_t *attr, size_t size)
{
  attr->s_size = size;
  return 0;
}

static void
test_cancel_locked (pthread_t t)
{
  struct _pthread_v *tv = __pth_gpointer_locked (t);

  if (!tv || tv->in_cancel || tv->ended != 0 || (tv->p_state & PTHREAD_CANCEL_ENABLE) == 0)
    return;
  if ((tv->p_state & PTHREAD_CANCEL_ASYNCHRONOUS) == 0)
    return;
  if (WaitForSingleObject(tv->evStart, 0) != WAIT_OBJECT_0)
    return;
  pthread_mutex_unlock (&tv->p_clock);
  _pthread_invoke_cancel();
}

int
pthread_setcancelstate (int state, int *oldstate)
{
  _pthread_v *t = __pthread_self_lite ();

  if (!t || (state & PTHREAD_CANCEL_ENABLE) != state)
    return EINVAL;

  pthread_mutex_lock (&t->p_clock);
  if (oldstate)
    *oldstate = t->p_state & PTHREAD_CANCEL_ENABLE;
  t->p_state &= ~PTHREAD_CANCEL_ENABLE;
  t->p_state |= state;
  test_cancel_locked (t->x);
  pthread_mutex_unlock (&t->p_clock);

  return 0;
}

int
pthread_setcanceltype (int type, int *oldtype)
{
  _pthread_v *t = __pthread_self_lite ();

  if (!t || (type & PTHREAD_CANCEL_ASYNCHRONOUS) != type)
    return EINVAL;

  pthread_mutex_lock (&t->p_clock);
  if (oldtype)
    *oldtype = t->p_state & PTHREAD_CANCEL_ASYNCHRONOUS;
  t->p_state &= ~PTHREAD_CANCEL_ASYNCHRONOUS;
  t->p_state |= type;
  test_cancel_locked (t->x);
  pthread_mutex_unlock (&t->p_clock);

  return 0;
}

int
pthread_create_wrapper (void *args)
{
  unsigned rslt = 0;
  struct _pthread_v *tv = (struct _pthread_v *)args;

  pthread_mutex_lock (&mtx_pthr_locked);
  pthread_mutex_lock (&tv->p_clock);
  _pthread_once_raw(&_pthread_tls_once, pthread_tls_init);
  TlsSetValue(_pthread_tls, tv);
  tv->tid = GetCurrentThreadId();
  pthread_mutex_unlock (&tv->p_clock);


  if (!setjmp(tv->jb))
    {
      intptr_t trslt = (intptr_t) 128;
      /* Provide to this thread a default exception handler.  */
      #ifdef __SEH__
	asm ("\t.tl_start:\n"
	  "\t.seh_handler __C_specific_handler, @except\n"
	  "\t.seh_handlerdata\n"
	  "\t.long 1\n"
	  "\t.rva .tl_start, .tl_end, _gnu_exception_handler ,.tl_end\n"
	  "\t.text"
	  );
      #endif      /* Call function and save return value */
      pthread_mutex_unlock (&mtx_pthr_locked);
      if (tv->func)
        trslt = (intptr_t) tv->func(tv->ret_arg);
      #ifdef __SEH__
	asm ("\tnop\n\t.tl_end: nop\n");
      #endif
      pthread_mutex_lock (&mtx_pthr_locked);
      tv->ret_arg = (void*) trslt;
      /* Clean up destructors */
      _pthread_cleanup_dest(tv->x);
    }
  else
    pthread_mutex_lock (&mtx_pthr_locked);

  pthread_mutex_lock (&tv->p_clock);
  rslt = (unsigned) (size_t) tv->ret_arg;
  /* Make sure we free ourselves if we are detached */
  if (tv->evStart)
    CloseHandle (tv->evStart);
  tv->evStart = NULL;
  if (!tv->h)
    {
      tv->valid = DEAD_THREAD;
      pthread_mutex_unlock (&tv->p_clock);
      pthread_mutex_destroy (&tv->p_clock);
      push_pthread_mem (tv);
      tv = NULL;
      TlsSetValue (_pthread_tls, tv);
    }
  else
    {
      pthread_mutex_unlock (&tv->p_clock);
      pthread_mutex_destroy (&tv->p_clock);
      tv->ended = 1;
    }
  while (pthread_mutex_unlock (&mtx_pthr_locked) == 0)
   Sleep (0);
  _endthreadex (rslt);
  return rslt;
}

int
pthread_create (pthread_t *th, const pthread_attr_t *attr, void *(* func)(void *), void *arg)
{
  HANDLE thrd = NULL;
  int redo = 0;
  struct _pthread_v *tv;
  size_t ssize = 0;
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  if ((tv = pop_pthread_mem ()) == NULL)
    return EAGAIN;

  if (th)
    *th = tv->x;

  /* Save data in pthread_t */
  tv->ended = 0;
  tv->ret_arg = arg;
  tv->func = func;
  tv->p_state = PTHREAD_DEFAULT_ATTR;
  tv->h = INVALID_HANDLE_VALUE;
  /* We retry it here a few times, as events are a limited resource ... */
  do
    {
      tv->evStart = CreateEvent (NULL, 1, 0, NULL);
      if (tv->evStart != NULL)
	break;
      Sleep ((!redo ? 0 : 20));
    }
  while (++redo <= 4);

  tv->p_clock = PTHREAD_MUTEX_INITIALIZER;
  tv->spin_keys = new_spin_keys;
  tv->valid = LIFE_THREAD;
  tv->sched.sched_priority = THREAD_PRIORITY_NORMAL;
  tv->sched_pol = SCHED_OTHER;
  if (tv->evStart == NULL)
    {
      if (th)
       memset (th, 0, sizeof (pthread_t));
      push_pthread_mem (tv);
      return EAGAIN;
    }

  if (attr)
    {
      int inh = 0;
      tv->p_state = attr->p_state;
      ssize = attr->s_size;
      pthread_attr_getinheritsched (attr, &inh);
      if (inh)
	{
	  tv->sched.sched_priority = __pthread_self_lite ()->sched.sched_priority;
	}
      else
	tv->sched.sched_priority = attr->param.sched_priority;
    }

  /* Make sure tv->h has value of INVALID_HANDLE_VALUE */
  _ReadWriteBarrier();

  thrd = (HANDLE) _beginthreadex(NULL, ssize, (unsigned int (__stdcall *)(void *))pthread_create_wrapper, tv, 0x4/*CREATE_SUSPEND*/, NULL);
  if (thrd == INVALID_HANDLE_VALUE)
    thrd = 0;
  /* Failed */
  if (!thrd)
    {
      if (tv->evStart)
	CloseHandle (tv->evStart);
      pthread_mutex_destroy (&tv->p_clock);
      tv->spin_keys = new_spin_keys;
      tv->evStart = NULL;
      tv->h = 0;
      if (th)
        memset (th, 0, sizeof (pthread_t));
      push_pthread_mem (tv);
      return EAGAIN;
    }
  {
    int pr = tv->sched.sched_priority;
    if (pr <= THREAD_PRIORITY_IDLE) {
	pr = THREAD_PRIORITY_IDLE;
    } else if (pr <= THREAD_PRIORITY_LOWEST) {
	pr = THREAD_PRIORITY_LOWEST;
    } else if (pr >= THREAD_PRIORITY_TIME_CRITICAL) {
	pr = THREAD_PRIORITY_TIME_CRITICAL;
    } else if (pr >= THREAD_PRIORITY_HIGHEST) {
	pr = THREAD_PRIORITY_HIGHEST;
    }
    SetThreadPriority (thrd, pr);
  }
  ResetEvent (tv->evStart);
  if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0)
    {
      tv->h = 0;
      ResumeThread (thrd);
      CloseHandle (thrd);
    }
  else
    {
      tv->h = thrd;
      ResumeThread (thrd);
    }
  Sleep (0);
  return 0;
}

int
pthread_join (pthread_t t, void **res)
{
  DWORD dwFlags;
  struct _pthread_v *tv = __pth_gpointer_locked (t);
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags))
    return ESRCH;
  if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0)
    return EINVAL;
  if (pthread_equal(pthread_self(), t))
    return EDEADLK;

  /* pthread_testcancel (); */
  if (tv->ended == 0 || (tv->h != NULL && tv->h != INVALID_HANDLE_VALUE))
    WaitForSingleObject (tv->h, INFINITE);
  CloseHandle (tv->h);
  if (tv->evStart)
    CloseHandle (tv->evStart);
  tv->evStart = NULL;
  /* Obtain return value */
  if (res)
    *res = tv->ret_arg;
  pthread_mutex_destroy (&tv->p_clock);
  tv->spin_keys = new_spin_keys;
  push_pthread_mem (tv);

  return 0;
}

int
_pthread_tryjoin (pthread_t t, void **res)
{
  DWORD dwFlags;
  struct _pthread_v *tv;
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  pthread_mutex_lock (&mtx_pthr_locked);
  tv = __pthread_get_pointer (t);

  if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags))
    {
      pthread_mutex_unlock (&mtx_pthr_locked);
      return ESRCH;
    }

  if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0)
    {
      pthread_mutex_unlock (&mtx_pthr_locked);
      return EINVAL;
    }
  if (pthread_equal(pthread_self(), t))
    {
      pthread_mutex_unlock (&mtx_pthr_locked);
      return EDEADLK;
    }
  if(tv->ended == 0 && WaitForSingleObject(tv->h, 0))
    {
      if (tv->ended == 0);
        {
	  pthread_mutex_unlock (&mtx_pthr_locked);
	  /* pthread_testcancel (); */
	  return EBUSY;
	}
    }
  CloseHandle (tv->h);
  if (tv->evStart)
    CloseHandle (tv->evStart);
  tv->evStart = NULL;

  /* Obtain return value */
  if (res)
    *res = tv->ret_arg;
  pthread_mutex_destroy (&tv->p_clock);
  tv->spin_keys = new_spin_keys;

  push_pthread_mem (tv);

  pthread_mutex_unlock (&mtx_pthr_locked);
  /* pthread_testcancel (); */
  return 0;
}

int
pthread_detach (pthread_t t)
{
  int r = 0;
  DWORD dwFlags;
  struct _pthread_v *tv = __pth_gpointer_locked (t);
  HANDLE dw;
  pthread_spinlock_t new_spin_keys = PTHREAD_SPINLOCK_INITIALIZER;

  pthread_mutex_lock (&mtx_pthr_locked);
  if (!tv || tv->h == NULL || !GetHandleInformation(tv->h, &dwFlags))
    {
      pthread_mutex_unlock (&mtx_pthr_locked);
      return ESRCH;
    }
  if ((tv->p_state & PTHREAD_CREATE_DETACHED) != 0)
    {
      pthread_mutex_unlock (&mtx_pthr_locked);
      return EINVAL;
    }
  /* if (tv->ended) r = ESRCH; */
  dw = tv->h;
  tv->h = 0;
  tv->p_state |= PTHREAD_CREATE_DETACHED;
  _ReadWriteBarrier();
  if (dw)
    {
      CloseHandle (dw);
      if (tv->ended)
	{
	  if (tv->evStart)
	    CloseHandle (tv->evStart);
	  tv->evStart = NULL;
	  pthread_mutex_destroy (&tv->p_clock);
	  tv->spin_keys = new_spin_keys;
	  push_pthread_mem (tv);
	}
    }
  pthread_mutex_unlock (&mtx_pthr_locked);

  return r;
}

static int dummy_concurrency_level = 0;

int
pthread_getconcurrency (void)
{
  return dummy_concurrency_level;
}

int
pthread_setconcurrency (int new_level)
{
  dummy_concurrency_level = new_level;
  return 0;
}

